Below is a brief list of issues you may encounter when developing on the JVM and some methods for investigating them.

NoClassDefFoundError

We’ve seen multiple instances of this error when an attempt is made to load a class which does not exist on the classpath. There are two steps required to resolve such issues - understanding where you expect the missing class to be loaded from, and finding out what the classpath is.

In principle there’s no connection between a JAR file and the packages of .class files it contains. In practice however, packages tend to be grouped under the groupId or at least the artifact name of the published JAR. For example, the json-java JAR seen previously is published by the group org.json and all the classes it defines exist within the package org.json. Classes in the core Clojure JAR are defined under the package clojure. So a good place to start for investigating a missing class is to find the package name and search for dependencies with the same group or artifact id. Classpaths for even medium-sized projects can be quite large, and it’s not uncommon to come across unknown classes deep in the dependency tree.

Finding the classpath

Statically

Most project management tools such as Maven, Leiningen or tools.deps have tasks to output the current classpath. There are usually multiple classpaths within a project depending on the context. A common example is adding extra dependencies are resource directories when running tests. As we saw previously, the classpath for a Maven project can be retreived with

mvn dependency:build-classpath -Dmdep.outputFile=classpath.txt

At runtime

Within a running application, the classpath can be fetched via the system property java.class.path. If you are writing the application you can simply output it with e.g.

System.out.println(System.getProperty("java.class.path"));

If you’re investigating an application you didn’t write and can’t modify, you can get the properties for a running process with jinfo. First locate the PID of the Java process - the jps command can be used to list all running Java processes

jps -v

then get the properties of the running process

jinfo -sysprops <PID>

again the java.class.path property contains the classpath of the process.

UnsupportedClassVersionError

This exception is thrown when trying to load a class file with a newer format than the executing JVM supports. The JVM specification lists the class file versions supported by each Java version. If you encounter this error, you have two options - find a version of the class compiled for older class file format supported by your JVM, or use a newer JVM which supports the existing class file format.

NoSuchMethodError

This error usually occurs when an application is compiled against one version of a class, but a different version is available at runtime. Consider a simple class to display a greeting:

version1/Greeter.java

public class Greeter {
    public void greet(String who) {
        System.out.println("Hello " + who + "!");
    }
}

after some time, a new version of this class is created with a new method:

version2/Greeter.java

public class Greeter {
    public void greet(String who) {
        System.out.println("Hello " + who + "!");
    }

    public void longGreet(String who) {
        System.out.println("Hello " + who + ", nice to meet you!");
    }
}

an application is created which uses the newer version:

GreetApp.java

public class GreetApp {
    public static void main(String[] args) {
        new Greeter().longGreet("world");
    }
}

compiling both class versions and the application against the newer version

pushd version1 && javac Greet.java && popd
pushd version2 && javac Greet.java && popd
javac -cp .:version2 GreetApp.java

running the application with the new version on the classpath works as expected:

java -cp .:version2 GreetApp

running with the old version on the classpath results in an error:

> java -cp .:version1 GreetApp
Exception in thread "main" java.lang.NoSuchMethodError: 'void Greeter.longGreet(java.lang.String)'
	at GreetApp.main(GreetApp.java:3)

Some libraries deprecate and subsequently remove methods from classes, so it is possible that a class newer than the one expected is loaded instead. This error is usually the result of a mismatch between the expected and actual version of a library being loaded at runtime.

The first diagnostic step is finding out where the class is being loaded from. This can be done by supplying the -verbose:class option to the JVM

java -verbose:class ...

This outputs the location each class is loaded from. Writing this to file or filtering with grep should allow you to find where the class is located on the classpath. This will usually be a JAR file, and if fetched from a Maven repository will contain the artifact name and version number in the name.

The next step is deciding where you expect the class to be loaded from - this might be a different library entirely, or a different version of the same library.

The final step is to fix the classpath so the class is loaded from the expected location. This may involve excluding a transitive dependency somewhere in your dependency list.

ServiceLoader errors

Java ServiceLoaders an extensible mechanism for locating and loading service classes at runtime. They work by first defining an interface or class e.g.

package com.picosoft.messaging;

public interface MessageSourceFactory {
    ...
}

then by naming implementing classes in a file provided by implementation JARs. This file should exist at META-INF/services/{service class binary name} within the implementation JAR file. For example, a JAR providing database sources might define the following classes:

package com.picosoft.messaging.db;
public class PostgresMessageSourceFactory implements MessageSourceFactory { ... }
public class MySqlMessageSourceFactory implements MessageSourceFactory { ... }

these would then be listed as implementation classes within the corresponding interface service file:

META-INF/services/com.picosoft.messaging

com.picosoft.messaging.db.PostgresMessageSourceFactory
com.picosoft.messaging.db.MySqlMessageSourceFactory

when the implementation JAR is on the classpath these classes can be located and loaded via the ServiceLoader interface.

Special care must be taken when building an uberjar containing service loader classes. Multiple implementation JARs will define the same META-INF/services/{service.class} file. The default conflict handling behaviour when building uberjars is ‘last one wins’ for files at the same path. For service loaders, conflicting files should be concatenated together so all implementation classes are listed in the final JAR.

Uberjar path conflicts

The service loader issue listed above is just a specific case of a more general issue when building uberjars - conflicts between files at the same path in multiple JARs must be resolved to produce the file in the output JAR. Use the jar tool to inspect files within JARs to ensure they contain what you expect.