How to debug java.lang.ClassNotFoundException

java.lang.ClassNotFoundException is probably the most dreaded and difficult exception you can run into when doing Java development. What does it mean? Simply put, the class could not be found on the classpath. If you reached this far, the class was available during compile time, but mysteriously is not available during runtime. If you are using Maven, dependencies with provided scope are perfect candidates for this problem. From documentation:

A dependency with this scope is added to the classpath used for compilation and test, but not the runtime classpath.

If:

  • You have dependency on A in your pom.xml
  • A has dependency on B in its pom.xml
  • B is declared with a scope of provided in A’s pom.xml
  • Your pom.xml does not import B (with scope compile or runtime)

then you will get this error.

The classpath defines all the paths or places where the ClassLoader looks to load a class.

Step 1: inspect the classpath

if you ran your program using java, the -cp option defines the classpath. So check what you specified in -cp. Further this classpath is available in the java.class.path system property. Print it out and verify the result is what you’d expect.

Out of habit, I run my programs using Maven using mvn exec:java plugin. Among other things, it adds all the dependencies to the classpath so that I don’t have to manually list all of them using -cp (the list can become quite large). However, from what I understand Maven adds dependencies to the classpath at runtime whereas the java.class.path system property returns the classpath when JVM was invoked. This means if you print java.class.path and the program was run via mvn exec:java you don’t get the full classpath. You only get:

/Library/Downloads/apache-maven-3.8.5/boot/plexus-classworlds-2.6.0.jar

or something related and not the classpath you would expect. Run mvn and specify the -X switch. Now you will see the classpath in the output of mvn. e.g.:

[DEBUG] Collected project classpath [/Users/xxx/java/memory-measurer-test/target/classes]
[DEBUG] Adding to classpath : /Users/xxx/java/memory-measurer-test/target/classes
[DEBUG] Adding project dependency artifact: memory-measurer to classpath
[DEBUG] Adding project dependency artifact: jsr305 to classpath
[DEBUG] Adding project dependency artifact: guava to classpath

Also look into mvn dependency:build-classpath. To get the full classpath at runtime you can try using the ClassGraph library like so:

        List<URI> classpath = new ClassGraph().getClasspathURIs();
        for (URI uri : classpath) {
            System.out.println(uri.toString());
        }

This way I was able to get full classpath of a program that is run via mvn exec:java plugin.

Step 2: Check if your jar dependency really has the class in it

If you have verified the classpath is not the problem i.e., your dependency exists in the classpath, a good idea is to unpack the dependency (assuming its a jar file) using tar -xvf and verify that the class is indeed contained in the jar file. A lot of times when we have a complex dependency graph, there are version conflicts and the version of the dependency that ends up in the classpath does not have some class you need. When this happens use the maven-shade-plugin to provide multiple versions of the same dependency to your application (some libraries will need version X and other will need version Y. maven-shade-plugin gives you a way to package both versions in your application).

One time when it never worked

When using a javaagent and running using Maven, I got a java.langClassNotFoundException and was never able to fix it. more here. Only conclusion I could draw is that when program is run through mvn exec:java the classpath does not propagate to the javaagent maybe because Maven adds dependencies to the classpath at runtime and by then the javaagent has already started (just my guess). So beware of this gotcha.

I confirmed my hunch by inspecting the source code of the mvn exec plugin. It can be found here. It is instructive to see how it modifies the classpath at runtime. The code that does this is:

public class ExecJavaMojo # the main class that runs when mvn exec:java is used

    public void execute() # the main function within the class
        URLClassLoader classLoader = getClassLoader();

            private URLClassLoader getClassLoader()
                this.addRelevantPluginDependenciesToClasspath( classpathURLs );
                this.addRelevantProjectDependenciesToClasspath( classpathURLs );
                this.addAdditionalClasspathElements( classpathURLs );

But this classpath does not propagate to the javaagent. In fact the exec plugin does not even start (or know anything about) the javaagent. I could not find any reference to javaagent in the exec plugin. The javaagent seems to be invoked and handled by core Maven. Studying the source code of the exec plugin is very valuable if you run into difficult problems while executing your Java programs. It is the ultimate way to debug. We have to get inside the code.

The problem could be Maven

Maven is a notoriously buggy tool [1]. I have run into all sorts of issues with it ranging from irreproducible to incorrect behavior. Don’t believe me? See this:

Long story short, if you have exhausted all other resources and are running into the exception while executing your application via Maven, try executing the application via java command.

java $JAVA_OPTS -cp ./target/classes:$CLASSPATH <your-main-class>

replace $CLASSPATH with the output from mvn dependency:build-classpath.

Below I try to document real-world cases when I ran into the exception and how I fixed it as it is excruciatingly painful to debug this and happens all the time.

Real world example #1

Sequence of steps:

  1. I ran mvn clean install on a multi-module project (module = maven modules not JPMS) and it ran successfully
  2. Then I tried to run a module using mvn exec:java and got Caused by: java.lang.ClassNotFoundException: xxx

This ought to be impossible but still has happened to me. Steps followed to troubleshoot the problem:

  1. Re-run mvn exec with -X switch (or use mvn dependency:build-classpath) and verify the dependency that contains the class is being added to the classpath. ✓
  2. Unzip the dependency jar file and verify it contains the class. ✓
  3. I then ran the program without using Maven and the exception still reproed. ❌ At this point I was at end of my wits.
  4. I then replaced ./target/classes with path to jar file in .m2 repository and now it passed! ✓ On closer inspection I observed when I got the error java.lang.ClassNotFoundException: xxx the class was missing the namespace prefix.

I cannot explain this any other way except that this is bug in Maven. I was using exec:3.1.0:java

Sometimes you just don’t know the dependency that contains the ClassNotFound

What to do in that case? Checkout honeybadger.bot


I hope you find some of the tips in this article useful in your debugging of java.lang.ClassNotFoundException

This entry was posted in Computers, programming, Software and tagged . Bookmark the permalink.

Leave a comment