1

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.

diziaq
  • 6,881
  • 16
  • 54
  • 96
  • module-info.java where is this located and how is this relevant here – anish sharma Jul 09 '21 at 10:37
  • @anishsharma The file is located in `src/main/java/module-info.java` as described in the question. It is relevant, because its presence affects behavior of the program. I would like to add modularity to the project without losing ability of reading resources. – diziaq Jul 09 '21 at 10:52
  • 1
    You don’t need the current thread and you don’t need a class loader. Try using `Main.class.getResourceAsStream("/abc/cde.txt")` (notice the initial slash in the string—Class.getResourceAsStream requires it, but ClassLoader.getResourceAsStream forbids it). – VGR Jul 09 '21 at 14:18
  • @VGR This is really helpful. And a bit confusing ))) Please post an answer. – diziaq Jul 09 '21 at 16:04
  • 1
    The package `abc` must also made accessible in module-info. And better place in resources a package `mypackage/abc`. And getting the resource can be done simpler as already mentioned. – Joop Eggen Jul 09 '21 at 18:43

1 Answers1

7

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.

VGR
  • 40,506
  • 4
  • 48
  • 63
  • I think it is a perfect answer. – diziaq Jul 10 '21 at 08:20
  • 2
    It’s worth mentioning that the original code was even worse, by using `Thread.currentThread().getContextClassLoader()` which is not guaranteed to be `MyApplication.class.getClassLoader()` (or any loader knowing `MyApplication`) at all. This could break in many ways, even before Java 9. – Holger Aug 05 '21 at 14:30