Search code examples
javajava-iojava-resources

Java : getClass().getResource().toURI() Vs getClass().getResourceAsStream()


I have a java multiple modules sbt project and some of them contains a resources folder.

module-1
  resources
    template.xls
module-2
  resources
    other.xls

After packaging I got :

lib/module-1.jar
lib/module-2.jar

And I run my program like any other java application : java -cp "lib/*" MainClass.

My problem is accessing the template.xls from module-2.jar.

At first, I've tried the lines below to get my template :

URI template = getClass().getResource("/template.xls").toURI();
Files.newInputStream(Paths.get(template), StandardOpenOption.READ);

In development mode it works. But not on the server (after deployment), it cannot find the resource.

java.nio.file.FileSystemNotFoundException: null [jar:file:/.../lib/module-1.jar!/template.xls]

After some research I modified my accessing code like the following to get it works in both modes (development and deployed) :

InputStream templateIS = getClass().getResourceAsStream("/template.xls");

I cannot understand why !

What is the difference between the two methods ?


Solution

  • Files.newInputStream, as the name suggests, can open files. It cannot open anything else. It's just for files.

    The concept of an InputStream is much more abstract. When you open a file for reading, you get an inputstream, yup. But you also get inputstreams for many other things: Reading from a network connection; reading the unpacked contents of zip files. Reading the output of a decryption operation. The name says it all really: It's input, and it's a stream of data. Which applies to so much more than 'file on a filesystem'.

    Paths.get(template) produces a path object, which represents a file on the file system. If template is derived from a URI, this does not work unless the URI that you have so happens to be a URI to a file object; most URIs are not to file objects.

    Putting it all together, in your first sample, you find a resource on the classpath (which can be files, but don't have to be. For example, they could be entries in a jar file), you then ask its URI, feed it to the Paths API to turn it into a Path object, and then ask the Files API to turn this into an InputStream, which only works if the URI represents a file.

    In the second snippet, you just ask the classloader system to get you an inputstream. It knows how to do that (after all, java has to load those class files!). If the resource you're asking for so happens to be represented by a file, it's going to do, internally, more or less the same thing as your first snippet: Use the Files API to open the file for reading. But if it's anything else, it knows how to do that too – it also knows how to get resources across a network, from inside jar files, generated on-the-fly – the concept of class loading (which is what class.getResource lets you access) is abstracted away.

    NB: You're using it wrong. The proper way is ClassYouAreWritingIn.class.getResource and ClassYouAreWritingIn.class.getResourceAsStream; getClass().getResource is not correct; that breaks when subclassing, whereas the correct form doesn't.