Why would ClassLoader.getResourceAsStream() return null?
Asked Answered
T

2

7

Having the following code broken deliberately to identify the source of a NullPointerException in something that should have been very simple but turns out to drive me nuts:

Properties properties = new Properties();
Thread currentThread = Thread.currentThread();
ClassLoader contextClassLoader = currentThread.getContextClassLoader();
InputStream propertiesStream = contextClassLoader.getResourceAsStream("resource.properties");
if (propertiesStream != null) {
  properties.load(propertiesStream);
  // TODO close the stream
} else {
  // Properties file not found!
}

I get the "Properties file not found!" error, i.e. contextClassLoader.getResourceAsStream("resource.properties"); returns null.

This is a CXF-based client and I verified that the "resource.properties" file is in the current directory in which the client's jar resides (and runs).

I also verified the absolute path by including the following diagnostic code:

            File file = new File("resource.properties");
            System.out.println(file.getAbsolutePath());

The absolute path points to where the client's jar is.

I also tried finding out the context of the class loader, using:

  System.out.println(Thread.currentThread().getContextClassLoader());

but instead some directory structure as demonstrated here, all I get is:

com.simontuffs.onejar.JarClassLoader@1decdec

Why would ClassLoader.getResourceAsStream() return null?

What am I missing?

Tel answered 9/4, 2014 at 18:31 Comment(2)
Because the named resource isn't available via the CLASSPATH.Abuttal
Which CLASSPATH? I printed out the the classpath env var and it includes the current directory ("."). I am confused.Tel
T
10

I solved the mystery.

The key to solving was embedding some diagnostic logging when propertiesStream is null:

String classpath = System.getProperty("java.class.path");
LOG.info("CLASSPATH: " + classpath);
ClassLoader loader = MyClientMain.class.getClassLoader();
System.out.println("ClassLoader resource path: " + loader.getResource("resource.properties"));                    

So when I run with the original

contextClassLoader.getResourceAsStream("resource.properties")

I receive the null pointer condition, printing:

  INFO: CLASSPATH: myproj.one-jar.jar
  ClassLoader resource path: null

.

I then started suspecting something related to the "jar within a jar" as this is what the com.simontuffs.onejar essentially does (i.e. wrapping my project's jar inside a jar that contains all other library jars), so I opened myproj.one-jar.jar with 7-Zip and noted the full (absolute) path of "resource.properties":

myproj.one-jar.jar\main\myproj.jar\webapp\WEB-INF\classes\resource.properties

.

So I modified getResource("resource.properties") to:

 getResource("/main/myproj.jar/webapp/WEB-INF/classes/resource.properties")

which didn't fix the problem but printed the following upon the null pointer condition:

INFO: CLASSPATH: myproj.one-jar.jar
ClassLoader resource path: jar:file:/myproj.one-jar.jar!/main/myproj.jar!//main/myproj.jar/webapp/WEB-INF/classes/resource.properties

.

Then... divine intervention fell upon me and I had the insight (not reading any documentation that could even hint this, I swear!) that I should be using this path instead:

 getResource("/webapp/WEB-INF/classes/resource.properties")

And Voila! It works.

Whew.

Tel answered 10/4, 2014 at 14:50 Comment(2)
Moral of the story: While WAR files loading a resource from the same exact path only need the filename (i.e. the /webapp/WEB-INF/classes/ is implied and automagically determined), JAR files require the entire path.Tel
2 1/2 years later, I came along after reading on this in at least 20 other locations, none of which had solved my problem. But you did: adding "/webapp" to the front of the string solved my problem. I haven't seen that documented anywhere, so I have no idea how you came by your insight, but this got me moving again after 1.5 hours of being stuck. Thank you!!Pythagorean
A
2

As EJP pointed out, it means that the resource isn't available via the classpath for this particular classloader (different classloaders can have different classpaths).

Since the classloader is a JarClassLoader, it will only be able to load resources that are included inside the jar file. It won't see files that are in the same directory as the jar file.

Alf answered 9/4, 2014 at 18:54 Comment(6)
Thank you and that's exactly what I originally did. I put the file in both the src/main/resources and the webapp/WEB-INF/classes directories of the project, properly included in the JAR file. To no avail. What am I missing? Is it possible that the com.simontuffs.onejar.JarClassLoader modifies the classpath in some way? If so, how do I find the de-facto classpath?Tel
Check that the resource.properties file is included inside the jar file (if you wish to use that classloader to load it). Also try to load it as /resource.properties (if it's in the root of the jar).Alf
Yes, it is included (in both places, at least for the debug phase) and I tried both resource.properties and /resource.properties. To no avail. Something wicked is going on. I am suspecting something very specific to that "simontuffs"... How can I print the current classpath as the client application sees it at runtime?Tel
There is no single classpath. It all depends on the ClassLoader. It can delegate to its parent ClassLoader if it can't find it by itself, but I suspect that JarClassLoader (like URLClassLoader) doesn't delegate to its parent. You could use the getPackages() method to see which packages the classloader knows, and then make sure that the resource is in the jar at the proper sub directory (i.e. if the classloader knows a package com.foo.bar, the file should be inside the jar in com/foo/bar/resource.properties).Alf
I just used Package.getPackages() and it prints the world... i.e. hundreds of packages, but no path or a hint to a path. Still trying to figure out the proper sub directory. Additional suggestions on how to figure this out are much appreciated.Tel
Well, packages are also paths. Classes in package com.foo.bar will be in com/foo/bar/.Alf

© 2022 - 2024 — McMap. All rights reserved.