Sequentially Parallel

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:

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:

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