Reading files in a SpringBoot 2.0.1.RELEASE app
Asked Answered
P

3

4

I have a SpringBoot 2.0.1.RELEASE mvc application. In the resources folder I have a folder named /elcordelaciutat.

In the controller I have this method to read all the files inside the folder

ClassLoader classLoader = this.getClass().getClassLoader();
        Path configFilePath = Paths.get(classLoader.getResource("elcordelaciutat").toURI());    

        List<String> cintaFileNames = Files.walk(configFilePath)
         .filter(s -> s.toString().endsWith(".txt"))
         .map(p -> p.subpath(8, 9).toString().toUpperCase() + " / " + p.getFileName().toString())
         .sorted()
         .collect(toList());

        return cintaFileNames;

running the app. from Eclipse is working fine, but when I run the app in a Windows Server I got this error:

java.nio.file.FileSystemNotFoundException: null
    at com.sun.nio.zipfs.ZipFileSystemProvider.getFileSystem(ZipFileSystemProvider.java:171)
    at com.sun.nio.zipfs.ZipFileSystemProvider.getPath(ZipFileSystemProvider.java:157)
    at java.nio.file.Paths.get(Unknown Source)
    at 

I unzipped the generated jar file and the folder is there !

and the structure of the folders is

elcordelaciutat/folder1/*.txt
elcordelaciutat/folder2/*.txt
elcordelaciutat/folder3/*.txt
Postbellum answered 11/4, 2018 at 11:57 Comment(2)
Both phisch and Indra-basak are correct here. phisch is the reason you're getting the error, but it's because you're not fully utilizing Spring functionality; which is what Indra-basak is pointing out.Phosphaturia
This is an open issue on the spring team side : github.com/spring-projects/spring-boot/issues/7161Mazzola
A
5

I found the combination of ResourceLoader and ResourcePatternUtils to be the most optimum way of listing/reading files from a classpath resource folder in a Spring Boot application:

@RestController
public class ExampleController {

    private ResourceLoader resourceLoader;

    @Autowired
    public ExampleController(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    private List<String> getFiles() throws IOException {
        Resource[] resources = ResourcePatternUtils
                .getResourcePatternResolver(loader)
                .getResources("classpath*:elcordelaciutat/*.txt");

        return Arrays.stream(resources)
                   .map(p -> p.getFilename().toUpperCase())
                   .sorted()
                   .collect(toList());

    }
}

Updates

If you want to fetch all the files including the files in subfolders of elcordelaciutat, you need to include the following pattern classpath*:elcordelaciutat/**. This would retrieve the files in the subfolders including the subfolders. Once you get all the resources, filter them based on .txt file extension. Here are the changes you need to make:

private List<String> getFiles() throws IOException {
    Resource[] resources = ResourcePatternUtils
            .getResourcePatternResolver(loader)
            // notice **
            .getResources("classpath*:elcordelaciutat/**");

    return Arrays.stream(resources)
               .filter(p -> p.getFilename().endsWith(".txt"))
               .map(p -> {
                   try {
                       String path = p.getURI().toString();
                       String partialPath = path.substring(
                           path.indexOf("elcordelaciutat"));
                       return partialPath;
                   } catch (IOException e) {
                            e.printStackTrace();
                   }

                   return "";
                })
               .sorted()
               .collect(toList());
}

Let's say if you have the following resources folder structure:

+ resources
  + elcordelaciutat
    - FileA.txt
    - FileB.txt
    + a-dir
      - c.txt
      + c-dir
        - d.txt
    + b-dir
      - b.txt

After filtering, the list will contain the following strings:

  • elcordelaciutat/FileA.txt
  • elcordelaciutat/FileB.txt
  • elcordelaciutat/a-dir/c-dir/d.txt
  • elcordelaciutat/a-dir/c.txt
  • elcordelaciutat/b-dir/b.txt

Note

When you want to read a resource, you should always use the method getInputStream().

Ambivalence answered 13/4, 2018 at 23:34 Comment(2)
Note that there inside elcordelaciutat there are also subfolders and the text files inside the subfoldersPostbellum
@enPeris..ElgothdelaCiutat Updated my posting with the answer to your question.Ambivalence
C
10

When you start your project from Eclipse, the generated class files and resources are actually just files and folders on your hard drive. This is why it works to iterate over these files with the File class.

When you build a jar, all content is actually ziped and stored in a single archive. You can not access is with file system level tools any more, thus your FileNotFound exception.

Try something like this with the JarURL:

JarURLConnection connection = (JarURLConnection) url.openConnection();
JarFile file = connection.getJarFile();
Enumeration<JarEntry> entries = file.entries();
while (entries.hasMoreElements()) {
    JarEntry e = entries.nextElement();
    if (e.getName(). endsWith("txt")) {
        // ...
   }
}
Cm answered 11/4, 2018 at 13:22 Comment(1)
This is the correct answer. You are trying to access a file system path from your jar. You will not be able to do that.Lighting
A
5

I found the combination of ResourceLoader and ResourcePatternUtils to be the most optimum way of listing/reading files from a classpath resource folder in a Spring Boot application:

@RestController
public class ExampleController {

    private ResourceLoader resourceLoader;

    @Autowired
    public ExampleController(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    private List<String> getFiles() throws IOException {
        Resource[] resources = ResourcePatternUtils
                .getResourcePatternResolver(loader)
                .getResources("classpath*:elcordelaciutat/*.txt");

        return Arrays.stream(resources)
                   .map(p -> p.getFilename().toUpperCase())
                   .sorted()
                   .collect(toList());

    }
}

Updates

If you want to fetch all the files including the files in subfolders of elcordelaciutat, you need to include the following pattern classpath*:elcordelaciutat/**. This would retrieve the files in the subfolders including the subfolders. Once you get all the resources, filter them based on .txt file extension. Here are the changes you need to make:

private List<String> getFiles() throws IOException {
    Resource[] resources = ResourcePatternUtils
            .getResourcePatternResolver(loader)
            // notice **
            .getResources("classpath*:elcordelaciutat/**");

    return Arrays.stream(resources)
               .filter(p -> p.getFilename().endsWith(".txt"))
               .map(p -> {
                   try {
                       String path = p.getURI().toString();
                       String partialPath = path.substring(
                           path.indexOf("elcordelaciutat"));
                       return partialPath;
                   } catch (IOException e) {
                            e.printStackTrace();
                   }

                   return "";
                })
               .sorted()
               .collect(toList());
}

Let's say if you have the following resources folder structure:

+ resources
  + elcordelaciutat
    - FileA.txt
    - FileB.txt
    + a-dir
      - c.txt
      + c-dir
        - d.txt
    + b-dir
      - b.txt

After filtering, the list will contain the following strings:

  • elcordelaciutat/FileA.txt
  • elcordelaciutat/FileB.txt
  • elcordelaciutat/a-dir/c-dir/d.txt
  • elcordelaciutat/a-dir/c.txt
  • elcordelaciutat/b-dir/b.txt

Note

When you want to read a resource, you should always use the method getInputStream().

Ambivalence answered 13/4, 2018 at 23:34 Comment(2)
Note that there inside elcordelaciutat there are also subfolders and the text files inside the subfoldersPostbellum
@enPeris..ElgothdelaCiutat Updated my posting with the answer to your question.Ambivalence
N
3
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.util.StreamUtils;
...
private static final String CURRENT_FILE = "file.txt";

public Resource getCurrentResource() {
    return new ClassPathResource(CURRENT_FILE);
}

Later in client code or in code of some other module code you can even get its bytes:

...
Resource resource = getCurrentResource();
byte[] data = StreamUtils.copyToByteArray(resource.getInputStream());
Nieberg answered 17/4, 2018 at 13:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.