Loading classes and resources in Java 9

Viewed 12977

I was reading this article on InfoQ quoting Reinhold:

Developers can still use the Java class path in Java 9 for the Java runtime to search for classes and resource files. It's just that with Java 9's modules, developers no longer need the class path.

So now my question is: what is the proper Java 9 way to do the tasks listed above? How do you dynamically load e.g. an image (short of fiddling with relative paths)?

Even more interestingly, how would one check if a class is available and make a decision dynamically (e.g. check if Jackson is available and, if so, use it for JSON serialization and if not use something else)?

The article also mentions Spring Boot already supporting Java 9, and Spring Boot definitely does a lot of dynamic loading. So maybe someone knows the priece of code from Spring that I can look at?

3 Answers

In addition to the existing answers I’d like to give an example of encapsulation rules for non-class-file resources for different resource directory names.

The specification for getResourceAsStream says that the resource is encapsulated if a package name is derived from its name.

So if the resource’s directory name is NOT a valid Java identifier, it is NOT encapsulated. Which means that if a module has a resource located under, for example, a directory named dir-1 (containing a non-valid character - in its name) it will always be accessible from outside of the module.

Here is an example of two Java modules (source code in GitHub). Module 1 consists of the following resource files:

├── dir-3
│   └── resource3.txt
├── dir1
│   └── resource1.txt
├── dir2
│   └── resource2.txt
└── root.txt

and module-info.java:

module module_one {
  opens dir1;
}

Module 2 requires Module 1 in module-info.java:

module module_two {
  requires module_one;
}

and has a sample main class for loading various resource files:

package module2;

import java.io.IOException;

public class Main {
  public static void main(String[] args) throws IOException {
    loadResource("root.txt", "From module's root directory");
    loadResource("dir1/resource1.txt", "From opened package `dir1`");
    loadResource("dir2/resource2.txt", "From internal package `dir2`");
    loadResource("dir-3/resource3.txt", "From directory `dir-3` with non-Java name");
  }

  public static void loadResource(String name, String comment) throws IOException {
    // module2 application class loader
    final var classLoader = Main.class.getClassLoader();
    try (var in = classLoader.getResourceAsStream(name)) {
      System.out.println();
      System.out.println("// " + comment);
      System.out.println(name + ": " + (in != null));
    }
  }
}

Running the class above gives the following output:



// From module's root directory
root.txt: true

// From opened package `dir1`
dir1/resource1.txt: true

// From internal package `dir2`
dir2/resource2.txt: false

// From directory `dir-3` with non-Java name
dir-3/resource3.txt: true

As you can see the resource file from the root directory and from the dir-3 directory are not encapsulated, therefore Module 2 can load them.

The package dir1 is encapsulated but unconditionally opened. Module 2 can load it as well.

The package dir2 is encapsulated and not opened. Module 2 cannot load it.

Note that Module 2 cannot contain their own resources under dir1 and dir2 directories because they are already encapsulated in Module 1. If you try adding dir1 you will get the following error:

Error occurred during initialization of boot layer
java.lang.LayerInstantiationException: Package dir1 in both module module_one and module module_two

Here is a Flyway related issue for reference.

Related