diziaq
diziaq

Reputation: 7825

Why Java cannot read same resource file when module-info is added?

There is a simple Java project with standard Maven folder structure.

src
  main
    java
      mypackage
        Main.java
    resource
      abc
        cde.txt  

Main.java (boilerplate omitted)

var path = "abc/cde.txt";
InputStream input = Thread.currentThread().getContextClassLoader().getResourceAsStream(path);

if (input == null) {
    throw new IllegalStateException("Resource not found");
} else {
   // read from input
}

This code works fine and read file from the absolute path
"%project_root%/target/classes/abc/cde.txt" (compiled code).

After adding file src/main/java/module-info.java the situation changes: the program cannot find the file and throws in branch (input == null).

How to read files from "resource" folder the old way and have both: java-module and resources in the resource folder? I would like to avoid adding a prefix "src/main/resources" everywhere.

Upvotes: 1

Views: 1658

Answers (1)

VGR
VGR

Reputation: 44414

You probably want this:

InputStream input = Main.class.getResourceAsStream("/abc/cde.txt");

When you add a module-info.java, your classes are considered a module by Java.

A module has encapsulation restrictions beyond what a plain old classpath has. To access resources in a module, other code must go through that module, which will check whether the calling code’s module has permission to read those resources.

ClassLoader.getResourceAsStream will only read resources from explicitly opened modules:

Additionally … this method will only find resources in packages of named modules when the package is opened unconditionally.

But Class.getResource and Class.getResourceAsStream only rely on the module to which the class belongs, and don’t have that additional restriction.

One should always use Class.getResource or Class.getResourceAsStream. The ClassLoader equivalents should be avoided.

There is an important difference between the Class methods and the ClassLoader methods: The Class methods treat the argument as relative to the class’s package, unless the argument starts with a slash (/).

Aside from the encapsulation restrictions, given a class named com.example.MyApplication, these two lines are equivalent:

MyApplication.class.getResource("data.txt")
MyApplication.class.getClassLoader().getResource("com/example/data.txt")

And these are equivalent:

MyApplication.class.getResource("/data.txt")
MyApplication.class.getClassLoader().getResource("data.txt")

Again, they are only equivalent in terms of the resource path; the modular encapsulation restrictions are not the same. Always use the Class.getResource* methods, and avoid the ClassLoader.getResource* methods.

Upvotes: 9

Related Questions