Resources Demystified
I wanted to add a default configuration file to a Java project I was working on. This file would work as a fallback, in case the user did not provide their own configuration file.
For the fallback file to be bundled into the final jar, I added the file to the resources folder
of the project. The project looked like this:
src/
main/
java/
com/github/lpedrosa/myapp
Application.java
resources/
application.properties
This setup allow us to access the file as a resource in Java code:
InputStream resource = Application.class.getResourceAsStream("application.properties");
var properties = new Properties();
properties.load(resource);
Pretty cool huh? And when you run the application:
Exception in thread "main" java.lang.NullPointerException: inStream parameter is null
at java.base/java.util.Objects.requireNonNull(Objects.java:259)
at java.base/java.util.Properties.load(Properties.java:408)
at com.github.lpedrosa.myapp.Application.main(Application.java:12)
Wait a second…
The file is in the resources folder, so I should be able to access it. I mean, isn’t that what
the getResourceAsStream method is supposed to do?
Let’s look at what the javadocs say:
Returns:
A InputStream object; null if no resource with this name is found (…)
That should explain the NullPointerException on the Properties#load(InputStream) method. But
what else am I doing wrong?
What is a resource?
According to Oracle’s technotes1, a resource is data (images, audio, text, and so on) that a program needs to access in a way that is independent of the location of the program code.
The application.properties file we tried to load is a text file, so it should be fine.
The previous definition also mentions something about the location of a resource and the strategy behind accessing a resource. Let’s look at that folder structure again:
src/
main/
resources/
application.properties
This folder structure respects Maven’s Standard Directory Layout, which the two biggest java build tools (Maven and Gradle) respect and use, in order to build java projects.
If we use maven to build our example project, it creates a target folder that looks like this:
target/
classes/
com/github/lpedrosa/myapp
Application.class
application.properties
It looks like our application.properties file is at the root of the classes folder. Let’s look
at how we were retrieving the resource again:
Application.class.getResourceAsStream("application.properties");
The way Class#getResourceAsStream tries to find the resource by name is a bit complex. In our
example, this method will try to find a resource in either the module path or the class path of
where the Application class is located:
- our
Applicationclass is in thecom.github.lpedrosa.myapppackage - so the name given will resolve to
com/github/lpedrosa/myapp/application.properties
So how do I access a file at the root of the classes folder?
// note the slash (/) before the name
Application.class.getResourceAsStream("/application.properties");
If we run the application now, everything should work fine.
So a resource is just like a file?
While it may look like accessing a resource with the Class#getResourceAsStream method obeys the
same rules as traditional filesystem access, there is a catch.
Once our project is packaged as a jar, the resources contained inside become read-only2.
If you want to change the resources inside a jar you can either:
- change the project files and re-package your project;
- use a tool like
unziporjarto extract the resources as files
Can I use File I/O to read a resource?
No. Use the getResourceAsStream to obtain an InputStream and use the Reader family to read the
contents of the resource.
For example, if you wanted to print all the lines of the application.properties file:
InputStream resource = Application.class.getResourceAsStream("application.properties");
try (var reader = new BufferedReader(new InputStreamReader(resource))) {
// dump the file on stdout
reader.lines().forEach(System.out::println);
} catch(IOException e) {
// handle errors
};
Conclusion
- A java resource is read-only
- The resource’s location inside the JAR or classpath is important in order to access it
- You should not use File I/O to read a resource
https://docs.oracle.com/javase/8/docs/technotes/guides/lang/resources.html ↩︎
Check the posts by Tim Holloway, which clarify how a resource is different from a file ↩︎