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?

Community
  • 1
  • 1
Withheld
  • 4,603
  • 10
  • 45
  • 76

2 Answers2

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.

Withheld
  • 4,603
  • 10
  • 45
  • 76
  • 1
    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. – Withheld Apr 10 '14 at 14:54
  • 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!! – mcherm Jan 29 '17 at 16:18
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.

Kayaman
  • 72,141
  • 5
  • 83
  • 121
  • 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? – Withheld Apr 09 '14 at 19:00
  • 1
    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). – Kayaman Apr 09 '14 at 19:02
  • 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? – Withheld Apr 09 '14 at 19:05
  • 1
    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). – Kayaman Apr 09 '14 at 19:17
  • 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. – Withheld Apr 09 '14 at 19:55
  • Well, packages are also paths. Classes in package com.foo.bar will be in com/foo/bar/. – Kayaman Apr 10 '14 at 06:54