Reading a Resource File from Within Jar

Reading a resource file from within jar

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

Reading a resource file from either a "regular" file or a jarred file

One cannot turn a resource "file" inside a jar into a File. Only when classpath is immediately on classes, such as an unpacked war file. Such a resource has an URL like file:jar:/.../xxx.jar!.../yyy.txt

However, one could use Path & a file system view to copy a Path from a resource.

In your code it suffices to just use getResourceAsStream.

public static BufferedReader getBufferedReader(String fileToFind,
ClassLoader classLoader) {
InputStream inputStream = classLoader.getResourceAsStream(fileToFind);
InputStreamReader streamReader = new InputStreamReader(inputStream,
StandardCharsets.UTF_8);
return new BufferedReader(streamReader);
}

Mind fileToFind should not start with a / and should be an absolute path, when using a ClassLoader.

I specified the Charset, as you are taking the default, which would differ on a Linux production server, and a local Windows developer's machine.

Getting resource file from inside jar

Maven uses something called the Standard Directory Layout. If you don't follow this layout then the plugins can't do their job correctly. Technically, you can configure Maven to use different directories but 99.999% of the time this is not necessary.

One of the features of this layout is that production files go in:

  • <project-dir>/src/main/java

    • All *.java files
  • <project-dir>/src/main/resources

    • All non-*.java files (that are meant to be resources)

When you build your project the Java source files are compiled and the *.class files are put into the target/classes directory; this is done by the maven-compiler-plugin. Meanwhile, the resource files are copied from src/main/resources into target/classes as well; the maven-resources-plugin is responsible for this.

Note: See Introduction to the Build Lifecycle for more information about phases and which plugins are executed by which phase. This Stack Overflow question may also be useful.

When you launch your application from the IDE (possibly via the exec-maven-plugin) the target/classes directory is put on the classpath. This means all the compiled classes from src/main/java and all the copied resources from src/main/resources are available to use via the classpath.

Then, when you package your application in a JAR file, all the files in target/classes are added to the resulting JAR file (handled by the maven-jar-plugin). This includes the resources copied from src/main/resources. When you launch the application using this JAR file the resources are still available to use via the classpath, because they're embedded in the JAR file.

To make resource.txt available on the classpath, just move:

<project-dir>/resource.txt

To:

<project-dir>/src/main/resources/resource.txt.

Then you can use Class#getResource with /resource.txt as the path and everything should work out for you. The URL returned by getResource will be different depending on if you're executing against target/classes or against the JAR file.

When executing against target/classes you'll get something like:

file:///.../<project-dir>/target/classes/resource.txt

When executing against the JAR file you'll get something like:

jar:file:///.../<project-dir>/target/projectname-version.jar!/resource.txt

Note: This all assumes resource.txt is actually supposed to be a resource and not an external file. Resources are typically read-only once deployed in a JAR file; if you need a writable file then it's up to you to use a designated location for the file (e.g. a folder in the user's home directory). One typically accesses external files via either java.io.File or java.nio.file.*. Remember, resources are not the same thing as normal files.

Now, if you were to put resource.txt directly under <project-dir> that would mean nothing to Maven. It would not be copied to target/classes or end up in the JAR file which means the resource is never available on the classpath. So just to reiterate, all resources go under src/main/resources.


Check out the Javadoc of java.lang.Class#getResource(String) for more information about the path, such as when to use a leading / and when not to. The link points to the Javadoc for Java 12 which includes information about resources and modules (JPMS/Jigsaw modules, not Maven modules); if you aren't using modules you can ignore that part of the documentation.

How to get a path to a resource in a Java JAR file

This is deliberate. The contents of the "file" may not be available as a file. Remember you are dealing with classes and resources that may be part of a JAR file or other kind of resource. The classloader does not have to provide a file handle to the resource, for example the jar file may not have been expanded into individual files in the file system.

Anything you can do by getting a java.io.File could be done by copying the stream out into a temporary file and doing the same, if a java.io.File is absolutely necessary.

Jar can not access to resources folder

So you want to get all the files in a classpath resource folder? I found this example:

private static File[] getResourceFolderFiles (String folder) {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
URL url = loader.getResource(folder);
String path = url.getPath();
return new File(path).listFiles();
}

I tested it with System.out.println(Arrays.toString(getResourceFolderFiles("foldername/"))); and it works. But your folder has to be on classpath!

Get resource file from jar file

String fontFilePath = Paths.get(System.getProperty("user.dir"), "prova.jar", "Retro Gaming.ttf").toString();

That.. rather obviously won't work.

You need to use the gRAS (getResourceAsStream) system. File in java (as in, what new FileInputStream needs, the java.io.File object) are actual files. entries inside jar files don't count. It is not possible to refer to that ttf file with a File object, nor to open it with FileInputStream.

Fortunately, the createFont method doesn't demand that you pass a FileInputStream; any old InputStream will do.

The ttf file needs to be in the same classpath root as the this very class you are writing (for example, the same jar). Once you've ensured that is the case, you can use gRAS:

try (var fontIn = FontLoader.class.getResourceAsStream("/Retro Gaming.ttf")) {
Font.createFont(Font.TRUETYPE_FONT, fontIn).deriveFont(.., ...);
}

gRAS looks in the same place as where FontLoader.class lives. From your snippet it sounds like you put the ttf in the 'root' of the jar and not next to FontLoader. The leading slash in the string argument to getResourceAsStream means: Look relative to the root of the place FontLoader is in (so, your jar, presumably).



Related Topics



Leave a reply



Submit