← Debugging Guides

阅读中文版本

Java ClassNotFoundException — Missing Class on the Runtime Classpath

Trace ClassNotFoundException from packaging to runtime classpath by inspecting the jar, dependency tree, and class loader instead of adding random libraries.

intermediatedebuggingjavaclasspathjvm

Symptoms

  • java.lang.ClassNotFoundException: com.example.SomeClass
  • The class compiles and tests pass, but the packaged application fails at startup or first use
  • NoClassDefFoundError appears later for a class that was present during compilation
  • Only one deployment environment fails, such as the container image or the application server

Possible causes

  • The dependency is declared with provided or test scope and is absent at runtime
  • The executable jar was built without its dependencies, or the main class or classpath entry is wrong
  • Two versions of the same library are present and the wrong one wins on the classpath
  • A reflective or plugin class is loaded by a different class loader that cannot see the jar
  • The class file is missing from the artifact because resource filtering or shading excluded it

Diagnosis steps

Check whether the class is inside the deployed artifact

jar tf target/app.jar | grep 'com/example/SomeClass.class'

Interpretation

No output means packaging is the problem. A match shifts suspicion to the class loader or the classpath used to launch the application.

Print the runtime classpath of the running JVM

jcmd $(pgrep -f 'app.jar' | head -1) VM.system_properties | grep -i 'java.class.path'

Interpretation

Compare the entries with the build output. Missing directories, stale jars, or a wildcard classpath entry that does not match the layout explain the failure.

Inspect the dependency tree for scope and duplicates

mvn dependency:tree -Dincludes=com.example:some-lib; mvn dependency:tree -Dverbose | grep -E 'omitted|conflict' | head -20

Interpretation

A provided or test scope at runtime, or an omitted-for-duplicate entry, identifies the missing or overridden version.

Confirm the library is on the class loader that loads the caller

java -verbose:class -jar target/app.jar 2>&1 | grep -E 'SomeClass|SomeLib' | head -20

Interpretation

Absence of a load line means this loader never resolved the class. A load line with a different loader name points at container or module isolation.

Root cause

The JVM could not resolve the named class from the class loaders available to the calling code. The class was either never packaged, packaged under a different name, excluded by scope or shading, or loaded by a parent or plugin loader that has no visibility of the application jars.

Fix

Correct the dependency scope so the library is present at runtime, rebuild the artifact and verify the class is inside it, and launch with the intended classpath or fat jar. Remove duplicate versions with dependencyManagement or a BOM, and for application servers place shared libraries in the container classpath or use the container's documented module. Do not catch and ignore the exception; fix the packaging.

Risk note

Adding a jar that happens to contain the class can create split packages and linkage errors. Dropping a library into a container's shared directory affects every deployed application. Disabling verification or catching ClassNotFoundException hides a startup defect until a request fails in production.

Verify the fix

Run the packaged artifact in a clean container or a fresh JVM with no IDE classpath, exercise the code path that loads the class, and confirm startup and requests succeed. Repeat with the production image, and check that the dependency tree resolves exactly one version of the library.

Runtime classpath, not source code

ClassNotFoundException is thrown when Class.forName, a class loader, or the JVM cannot resolve a class by name at runtime. The compiler already found the type, so the source is usually fine. The contradiction between a green build and a failing deployment is the signature of a packaging or class-loader problem.

java.lang.ClassNotFoundException: com.example.payments.StripeClient
    at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:445)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:592)

A later NoClassDefFoundError for the same class means the first load failed and the JVM cached that failure. Always fix the first exception.

Prove the class exists at runtime

jar tf target/app.jar | grep 'com/example/payments/StripeClient.class'
mvn dependency:tree -Dincludes=com.stripe:stripe-java
java -verbose:class -jar target/app.jar 2>&1 | grep StripeClient

If jar tf finds nothing, inspect how the jar is assembled: a plain maven-jar-plugin artifact contains only project classes, so dependencies must be copied or shaded. If the class is present but never loads, capture jcmd $(pgrep -f app.jar | head -1) VM.system_properties and compare java.class.path with the intended launch command.

Scope and loader mistakes

provided scope is correct for APIs supplied by a servlet container and wrong for a library the application calls directly. Shading can relocate packages, so the original name disappears while the code still references it. Application servers and OSGi containers use their own delegation, and a plugin loaded by a child loader cannot see jars available only to the parent.

Fix and verify

Align the dependency scope, remove duplicate versions through dependencyManagement, rebuild, and confirm the class is inside the artifact. For containers, follow the documented shared-library mechanism instead of copying jars into an arbitrary directory.

Verify by running the packaged jar in a clean container with no IDE or local classpath, then exercise the failing code path. A successful start plus a request that uses the class is the only proof that the runtime classpath is correct.