ChatGPT解决这个技术问题 Extra ChatGPT

Reading a resource file from within jar

I would like to read a resource from within my jar like so:

File file;
file = new File(getClass().getResource("/file.txt").toURI());
BufferedReader reader = new BufferedReader(new FileReader(file));

//Read the file

and it works fine when running it in Eclipse, but if I export it to a jar, and then run it, there is an IllegalArgumentException:

Exception in thread "Thread-2"
java.lang.IllegalArgumentException: URI is not hierarchical

and I really don't know why but with some testing I found if I change

file = new File(getClass().getResource("/file.txt").toURI());

to

file = new File(getClass().getResource("/folder/file.txt").toURI());

then it works the opposite (it works in jar but not eclipse).

I'm using Eclipse and the folder with my file is in a class folder.

If you want to read files from a directory in jar with any numbers for files, see Stackoverflow-Link
I'm not sure that the original question was involving Spring. The link in the previous comment refers to a Spring specific answer from a different question. I believe getResourceAsStream is still a simpler and more portable solution to the problem.

L
Lii

Rather than trying to address the resource as a File just ask the ClassLoader to return an InputStream for the resource instead via getResourceAsStream:

try (InputStream in = getClass().getResourceAsStream("/file.txt");
    BufferedReader reader = new BufferedReader(new InputStreamReader(in))) {
    // Use resource
}

As long as the file.txt resource is available on the classpath then this approach will work the same way regardless of whether the file.txt resource is in a classes/ directory or inside a jar.

The URI is not hierarchical occurs because the URI for a resource within a jar file is going to look something like this: file:/example.jar!/file.txt. You cannot read the entries within a jar (a zip file) like it was a plain old File.

This is explained well by the answers to:

How do I read a resource file from a Java jar file?

Java Jar file: use resource errors: URI is not hierarchical


Thank you, this was very helpful and the code works perfectly, but I do have one problem, I need to determine whether the InputStream exists (like File.exists()) so my game can tell whether to use the default file or not. Thanks.
Oh and BTW the reason getClass().getResource("**/folder**/file.txt") made it work is because I had that folder in the same directory as my jar :).
getResourceAsStream returns null if the resource does not exist so that can be your "exists" test.
And of course... don't forget to close the inputStream and BufferedReader
You cannot read the entries within a jar (a zip file) like it was a plain old File. This sucks because there are a ton of library functions that expect a file path as an input.
J
Juozas Kontvainis

To access a file in a jar you have two options:

Place the file in directory structure matching your package name (after extracting .jar file, it should be in the same directory as .class file), then access it using getClass().getResourceAsStream("file.txt")

Place the file at the root (after extracting .jar file, it should be in the root), then access it using Thread.currentThread().getContextClassLoader().getResourceAsStream("file.txt")

The first option may not work when jar is used as a plugin.


Thank you very much for this great answer ... Another benefit from the second option is that it also works from the main method, because getClass() only works from an instance and not from static methods.
M
MForm

I had this problem before and I made fallback way for loading. Basically first way work within .jar file and second way works within eclipse or other IDE.

public class MyClass {

    public static InputStream accessFile() {
        String resource = "my-file-located-in-resources.txt";

        // this is the path within the jar file
        InputStream input = MyClass.class.getResourceAsStream("/resources/" + resource);
        if (input == null) {
            // this is how we load file within editor (eg eclipse)
            input = MyClass.class.getClassLoader().getResourceAsStream(resource);
        }

        return input;
    }
}

does not work for me ....I have .wsdl in the resources in jar ...but its not still not able to resolve the wsdl
N
Naor Bar

Up until now (December 2017), this is the only solution I found which works both inside and outside the IDE.

Use PathMatchingResourcePatternResolver

Note: it works also in spring-boot

In this example I'm reading some files located in src/main/resources/my_folder:

try {
    // Get all the files under this inner resource folder: my_folder
    String scannedPackage = "my_folder/*";
    PathMatchingResourcePatternResolver scanner = new PathMatchingResourcePatternResolver();
    Resource[] resources = scanner.getResources(scannedPackage);

    if (resources == null || resources.length == 0)
        log.warn("Warning: could not find any resources in this scanned package: " + scannedPackage);
    else {
        for (Resource resource : resources) {
            log.info(resource.getFilename());
            // Read the file content (I used BufferedReader, but there are other solutions for that):
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(resource.getInputStream()));
            String line = null;
            while ((line = bufferedReader.readLine()) != null) {
                // ...
                // ...                      
            }
            bufferedReader.close();
        }
    }
} catch (Exception e) {
    throw new Exception("Failed to read the resources folder: " + e.getMessage(), e);
}

u
user9869932

In my case I finally made it with

import java.lang.Thread;
import java.io.BufferedReader;
import java.io.InputStreamReader;

final BufferedReader in = new BufferedReader(new InputStreamReader(
      Thread.currentThread().getContextClassLoader().getResourceAsStream("file.txt"))
); // no initial slash in file.txt

Strangely enough this was also the solution in my case with nested resources. I wonder why it seems to work with the slash in the other posted answers though.
@ElectRocnic I think it's because how paths are interpreted. "/file.txt" says, switch to the current directory and open file.txt whereas "file.txt" says, open file.txt from current location. They both lead to the same location. For me, both works in a windows system.
aha, it doesn't work for me in a docker+linux environment... thanks for your statement
Simply getClass().getClassLoader().getResourceAsStream() worked for me
N
Navneeth

The problem is that certain third party libraries require file pathnames rather than input streams. Most of the answers don't address this issue.

In this case, one workaround is to copy the resource contents into a temporary file. The following example uses jUnit's TemporaryFolder.

    private List<String> decomposePath(String path){
        List<String> reversed = Lists.newArrayList();
        File currFile = new File(path);
        while(currFile != null){
            reversed.add(currFile.getName());
            currFile = currFile.getParentFile();
        }
        return Lists.reverse(reversed);
    }

    private String writeResourceToFile(String resourceName) throws IOException {
        ClassLoader loader = getClass().getClassLoader();
        InputStream configStream = loader.getResourceAsStream(resourceName);
        List<String> pathComponents = decomposePath(resourceName);
        folder.newFolder(pathComponents.subList(0, pathComponents.size() - 1).toArray(new String[0]));
        File tmpFile = folder.newFile(resourceName);
        Files.copy(configStream, tmpFile.toPath(), REPLACE_EXISTING);
        return tmpFile.getAbsolutePath();
    }

This is glossed over a lot. Thanks for documenting it. I'm curious what other options exist without JUnit.
Since the resourceName should always use / as separator, unlike the File operations inside decomposePath, which use the system specific file separator, the method is not only unnecessarily complicated compared to path.split("/"), it’s even incorrect. Further, it’s not clear why you invoke .toArray(new String[0])) when you don’t use its result.
what is folder, from which library is Lists, what is REPLACE_EXISTING...? Please provide full information on your posts.
P
Petterson

Make sure that you work with the correct separator. I replaced all / in a relative path with a File.separator. This worked fine in the IDE, however did not work in the build JAR.


佚名

I have found a fix

BufferedReader br = new BufferedReader(new InputStreamReader(Main.class.getResourceAsStream(path)));

Replace "Main" with the java class you coded it in. replace "path" with the path within the jar file.

for example, if you put State1.txt in the package com.issac.state, then type the path as "/com/issac/state/State1" if you run Linux or Mac. If you run Windows then type the path as "\com\issac\state\State1". Don't add the .txt extension to the file unless the File not found exception occurs.


V
Valentino Ricci

This code works both in Eclipse and in Exported Runnable JAR

private String writeResourceToFile(String resourceName) throws IOException {
    File outFile = new File(certPath + File.separator + resourceName);

    if (outFile.isFile())
        return outFile.getAbsolutePath();
    
    InputStream resourceStream = null;
    
    // Java: In caso di JAR dentro il JAR applicativo 
    URLClassLoader urlClassLoader = (URLClassLoader)Cypher.class.getClassLoader();
    URL url = urlClassLoader.findResource(resourceName);
    if (url != null) {
        URLConnection conn = url.openConnection();
        if (conn != null) {
            resourceStream = conn.getInputStream();
        }
    }
    
    if (resourceStream != null) {
        Files.copy(resourceStream, outFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
        return outFile.getAbsolutePath();
    } else {
        System.out.println("Embedded Resource " + resourceName + " not found.");
    }
    
    return "";
}   

What is certPath here?
M
Mahdi

finally i solved errors:

String input_path = "resources\\file.txt";
        
        input_path = input_path.replace("\\", "/");  // doesn't work with back slash
        
        URL file_url = getClass().getClassLoader().getResource(input_path);
        String file_path = new URI(file_url.toString().replace(" ","%20")).getSchemeSpecificPart();
        InputStream file_inputStream = file_url.openStream();

Please consider adding more information about your solution and why it works
s
sendon1982

You can use class loader which will read from classpath as ROOT path (without "/" in the beginning)

InputStream in = getClass().getClassLoader().getResourceAsStream("file.txt"); 
BufferedReader reader = new BufferedReader(new InputStreamReader(in));

R
Ram C

For some reason classLoader.getResource() always returned null when I deployed the web application to WildFly 14. getting classLoader from getClass().getClassLoader() or Thread.currentThread().getContextClassLoader() returns null.

getClass().getClassLoader() API doc says,

"Returns the class loader for the class. Some implementations may use null to represent the bootstrap class loader. This method will return null in such implementations if this class was loaded by the bootstrap class loader."

may be if you are using WildFly and yours web application try this

request.getServletContext().getResource() returned the resource url. Here request is an object of ServletRequest.


T
Tristan

If you are using spring, then you can use the the following method to read file from src/main/resources:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.springframework.core.io.ClassPathResource;

  public String readFileToString(String path) throws IOException {

    StringBuilder resultBuilder = new StringBuilder("");
    ClassPathResource resource = new ClassPathResource(path);

    try (
        InputStream inputStream = resource.getInputStream();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) {

      String line;

      while ((line = bufferedReader.readLine()) != null) {
        resultBuilder.append(line);
      }

    }

    return resultBuilder.toString();
  }

Welcome to SO. This does not provide an answer to the question. Once you have sufficient reputation you will be able to comment on any post. Also check this what can I do instead.
This will remove the line breaks in the file !
o
oguz ismail

Below code works with Spring boot(kotlin):

val authReader = InputStreamReader(javaClass.getResourceAsStream("/file1.json"))

p
pablo.vix

If you wanna read as a file, I believe there still is a similar solution:

    ClassLoader classLoader = getClass().getClassLoader();
    File file = new File(classLoader.getResource("file/test.xml").getFile());

URL.getFile() does not convert a URL to a file name. It returns the portion of the URL after the host, with all percent-encodings intact, so if the path contains any non-ASCII characters or any ASCII characters not allowed in URLs (including spaces), the result will not be an existing file name, even if the URL is a file: URL.
this does not work inside once the program is build to a jar
Doesn't work from jar unless you convert to string and save it locally first.