How Do I Fix Java ClassNotFoundException and NoClassDefFoundError?
How do I fix Java ClassNotFoundException and NoClassDefFoundError?
TL;DR
- Bottom line:
ClassNotFoundExceptionis a checked exception thrown during explicit dynamic class loading (Class.forName(),ClassLoader.loadClass());NoClassDefFoundErroris an unchecked error thrown by the JVM when a class present at compile time is missing at runtime. Both are ultimately classpath problems, but they require different diagnostic approaches. - Key tool/command:
mvn dependency:tree(Maven) orgradle dependencies(Gradle) to find missing/conflicting dependencies, andjava -verbose:classto trace which classes the JVM loads and from where. - Watch out for:
NoClassDefFoundErrorcaused by a failed static initializer — the originalExceptionInInitializerErroronly appears on the first attempt; subsequent calls showNoClassDefFoundErrorwith no root cause. - Works with: Java 8+ (all current LTS versions: 8, 11, 17, 21). Java 9+ module system (JPMS) adds additional class visibility constraints.
Constraints
ClassNotFoundExceptionis a checked exception (extends ReflectiveOperationException extends Exception) that must be caught or declared;NoClassDefFoundErroris an error (extends LinkageError extends Error) that should not be caught for retry logic — it indicates a broken runtime environment. [src1, src2]- When
NoClassDefFoundErroris caused by a failed static initializer, the originalExceptionInInitializerErroris thrown only on the first class load attempt. All subsequent attempts throwNoClassDefFoundErrorwith no cause chain — check logs for the initialExceptionInInitializerError. [src3, src5] - In Java 9+ module system (JPMS), a class can be present on the module path but inaccessible without
exportsoropensdeclarations inmodule-info.java. Adding--add-opensor--add-exportsJVM flags is a workaround, not a fix. [src1] - Application servers (Tomcat, WildFly, WebSphere) use hierarchical classloaders. Placing a dependency in
the wrong scope (e.g.,
WEB-INF/libvs serverlib/) causesClassNotFoundExceptioneven when the JAR exists on disk. [src4, src5] - Never suppress
NoClassDefFoundErrorin a catch block and continue execution — the JVM's class resolution is cached, and the class will never load successfully in the same JVM session once it has failed. [src2, src3]
Quick Reference
| # | Cause | Likelihood | Signature | Fix |
|---|---|---|---|---|
| 1 | Missing dependency JAR | ~30% | ClassNotFoundException: com.example.SomeClass during Class.forName() or framework init |
Add the missing dependency to pom.xml/build.gradle or -cp [src4] |
| 2 | Dependency version mismatch | ~20% | NoClassDefFoundError: com/example/SomeClass — class existed at compile time but different JAR version at runtime |
Run mvn dependency:tree and resolve version conflicts [src4, src5] |
| 3 | Fat JAR packaging issue | ~15% | NoClassDefFoundError after mvn package / gradle build — dependencies not bundled |
Use maven-shade-plugin or spring-boot-maven-plugin; Gradle: shadowJar [src5] |
| 4 | Failed static initializer | ~10% | First: ExceptionInInitializerError; then repeated: NoClassDefFoundError: Could not initialize class X |
Fix the exception in the static block/field initializer [src3, src5] |
| 5 | Wrong classloader scope (app server) | ~8% | ClassNotFoundException in Tomcat/WildFly despite JAR on disk |
Move JAR to correct classloader scope (WEB-INF/lib vs server lib/) [src4] |
| 6 | Typo in fully qualified class name | ~5% | ClassNotFoundException: com.example.MyClss (misspelled) in Class.forName() |
Fix the class name string; Java is case-sensitive [src4, src6] |
| 7 | Missing transitive dependency | ~4% | NoClassDefFoundError for a class you never imported directly |
Run mvn dependency:tree -Dverbose to find excluded/missing transitive deps [src4] |
| 8 | JDBC driver not on classpath | ~3% | ClassNotFoundException: com.mysql.cj.jdbc.Driver |
Add JDBC driver dependency; modern drivers auto-register via SPI [src4] |
| 9 | Java module system (JPMS) visibility | ~2% | ClassNotFoundException or IllegalAccessError in Java 9+ modular apps |
Add requires/exports/opens in module-info.java; use --add-opens as workaround [src1] |
| 10 | Classpath ordering conflict | ~2% | Intermittent NoClassDefFoundError — class exists in multiple JARs |
Use mvn dependency:tree to identify duplicates; exclude unwanted JARs [src4, src5] |
| 11 | ProGuard/R8 removed class | ~1% | ClassNotFoundException after obfuscation/minification |
Add -keep rules for dynamically loaded classes [src4] |
Decision Tree
START — ClassNotFoundException or NoClassDefFoundError?
│
├── ClassNotFoundException?
│ ├── Using Class.forName() / ClassLoader.loadClass()?
│ │ ├── YES → Check class name for typos; verify JAR on classpath (Cause #6, #1)
│ │ └── NO → Framework/container loading the class?
│ │ ├── YES → Check dependency scope (provided vs compile) and app server classloader (Cause #5)
│ │ └── NO → Check transitive dependencies: mvn dependency:tree (Cause #7)
│ └── JDBC driver class?
│ ├── YES → Add driver JAR; use SPI auto-registration (Cause #8)
│ └── NO → Verify exact FQCN and JAR presence on classpath
│
├── NoClassDefFoundError?
│ ├── Error contains "Could not initialize class"?
│ │ ├── YES → Failed static initializer (Cause #4)
│ │ │ └── Check logs for ExceptionInInitializerError; fix the static block
│ │ └── NO → Class missing at runtime but present at compile time
│ │ ├── Fat JAR? → Check packaging plugin (shade/spring-boot/shadow) (Cause #3)
│ │ ├── Dependency conflict? → mvn dependency:tree (Cause #2, #10)
│ │ └── App server? → Check classloader scope (Cause #5)
│ └── After ProGuard/R8?
│ ├── YES → Add -keep rules for affected classes (Cause #11)
│ └── NO → Run java -verbose:class to trace class loading
│
└── DEFAULT → Run java -verbose:class -cp ... MainClass and check which JARs are loaded
Step-by-Step Guide
1. Read the full stack trace and identify the error type
The error message tells you exactly which class is missing and whether it is a
ClassNotFoundException (checked exception, dynamic loading) or
NoClassDefFoundError (error, compile-time dependency missing at runtime). [src1,
src2]
# Search logs for the specific error
grep -E "ClassNotFoundException|NoClassDefFoundError|ExceptionInInitializerError" application.log
# Key patterns:
# java.lang.ClassNotFoundException: com.example.MyClass
# --> Dynamic loading failed (Class.forName, classloader)
# java.lang.NoClassDefFoundError: com/example/MyClass
# --> Class existed at compile time but missing/broken at runtime
# java.lang.ExceptionInInitializerError
# Caused by: <some RuntimeException>
# --> Static initializer failed; next load = NoClassDefFoundError
Verify: You have the exact fully qualified class name (e.g., com.example.MyClass) and the error type.
2. Find which JAR contains the missing class
Use your build tool or manual search to identify the JAR that should provide the class. [src4]
# Maven: search for class in local repository
find ~/.m2/repository -name "*.jar" -exec sh -c \
'jar tf "$1" | grep -q "com/example/MyClass.class" && echo "$1"' _ {} \;
# Gradle: search dependency cache
find ~/.gradle/caches -name "*.jar" -exec sh -c \
'jar tf "$1" | grep -q "com/example/MyClass.class" && echo "$1"' _ {} \;
# Or use Maven Central search: https://search.maven.org/
Verify: You know the exact groupId:artifactId:version that contains the class.
3. Check your dependency tree for conflicts
Dependency version conflicts are the #1 cause in Maven/Gradle projects. A transitive dependency may pull in a different version that lacks the needed class. [src4, src5]
# Maven: full dependency tree with conflict markers
mvn dependency:tree -Dverbose
# Maven: check effective classpath
mvn dependency:build-classpath
# Gradle: full dependency tree
./gradlew dependencies --configuration runtimeClasspath
# Gradle: dependency insight for a specific module
./gradlew dependencyInsight --dependency commons-lang3 --configuration runtimeClasspath
Verify: mvn dependency:tree shows the expected version without (omitted for conflict) markers.
4. Add or fix the dependency
Based on what you found, add the missing dependency or resolve the version conflict. [src4, src5]
<!-- Maven: pom.xml -->
<dependency>
<groupId>com.example</groupId>
<artifactId>missing-library</artifactId>
<version>2.1.0</version>
</dependency>
<!-- Force version to resolve conflicts -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>conflicting-library</artifactId>
<version>3.0.0</version>
</dependency>
</dependencies>
</dependencyManagement>
// Gradle: build.gradle
dependencies {
implementation 'com.example:missing-library:2.1.0'
}
// Force a specific version
configurations.all {
resolutionStrategy {
force 'com.example:conflicting-library:3.0.0'
}
}
5. Verify packaging includes all dependencies
For standalone deployments (fat JARs), ensure all dependencies are bundled. [src5]
<!-- Maven: spring-boot-maven-plugin (Spring Boot apps) -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<!-- Maven: maven-shade-plugin (non-Spring apps) -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.1</version>
<executions>
<execution>
<phase>package</phase>
<goals><goal>shade</goal></goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
// Gradle: shadow plugin for fat JARs
plugins {
id 'com.github.johnrengelman.shadow' version '8.1.1'
}
shadowJar {
archiveClassifier.set('')
mergeServiceFiles() // critical for SPI (JDBC drivers, etc.)
}
Verify: jar tf target/myapp.jar | grep com/example/MyClass.class confirms the class is in the packaged JAR.
6. Fix static initializer failures (NoClassDefFoundError: Could not initialize class)
If NoClassDefFoundError says "Could not initialize class", the real error is in a static block
or static field initializer. [src3, src5]
// BAD: static initializer that can fail at runtime
public class Config {
// NPE if env var missing → ExceptionInInitializerError then NoClassDefFoundError
private static final String VALUE = System.getenv("REQUIRED_VAR").toUpperCase();
}
// GOOD: defensive static initialization
public class Config {
private static final String VALUE;
static {
String env = System.getenv("REQUIRED_VAR");
VALUE = (env != null) ? env.toUpperCase() : "DEFAULT";
}
}
Verify: Search logs for the first ExceptionInInitializerError; fix the underlying exception, then restart the JVM.
Code Examples
Java: safe dynamic class loading with error handling
// Input: Fully qualified class name as String
// Output: Loaded Class object or clear diagnostic error
public static Class<?> loadClassSafely(String className) {
try {
// Try Thread context classloader first (works in app servers)
return Thread.currentThread()
.getContextClassLoader()
.loadClass(className);
} catch (ClassNotFoundException e1) {
try {
// Fallback to Class.forName (uses caller's classloader)
return Class.forName(className);
} catch (ClassNotFoundException e2) {
throw new RuntimeException(
"Class '" + className + "' not found. " +
"Check: (1) JAR on classpath, (2) spelling, (3) version.",
e2);
}
}
}
Maven: diagnosing and fixing dependency conflicts
<!-- Step 1: Run: mvn dependency:tree -Dverbose -Dincludes=com.google.guava -->
<!-- Step 2: Exclude the unwanted transitive version -->
<dependency>
<groupId>com.example</groupId>
<artifactId>library-a</artifactId>
<version>1.0.0</version>
<exclusions>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Step 3: Declare the correct version explicitly -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>33.0.0-jre</version>
</dependency>
Gradle: diagnosing and fixing dependency conflicts
// Run: ./gradlew dependencyInsight --dependency guava --configuration runtimeClasspath
// Force a specific version globally
configurations.all {
resolutionStrategy {
force 'com.google.guava:guava:33.0.0-jre'
}
}
// Or exclude from a specific dependency
dependencies {
implementation('com.example:library-a:1.0.0') {
exclude group: 'com.google.guava', module: 'guava'
}
implementation 'com.google.guava:guava:33.0.0-jre'
}
Anti-Patterns
Wrong: Catching NoClassDefFoundError and retrying
// BAD — NoClassDefFoundError is cached by the JVM; the class will NEVER load [src2, src3]
for (int i = 0; i < 3; i++) {
try {
return new com.example.MyService();
} catch (NoClassDefFoundError e) {
System.out.println("Retry " + (i + 1) + "...");
Thread.sleep(1000); // Pointless — same error every time
}
}
Correct: Fix the classpath and restart the JVM
// GOOD — fix the root cause, don't retry [src2, src3]
// 1. Add the missing JAR to the classpath
// 2. Restart the JVM (NoClassDefFoundError state is cached per session)
// 3. For static initializer failures, fix the static block and restart
Wrong: Using Class.forName() for JDBC drivers in modern Java
// BAD — unnecessary since JDBC 4.0 (Java 6+); driver auto-registers via SPI [src4]
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection(url, user, pass);
Correct: Rely on JDBC 4.0 SPI auto-discovery
// GOOD — just ensure the driver JAR is on the classpath [src4]
// JDBC 4.0+ drivers register via META-INF/services/java.sql.Driver
Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/mydb", "user", "pass");
Wrong: Using provided scope for runtime dependencies
<!-- BAD — 'provided' means the container supplies this [src4, src5] -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>33.0.0-jre</version>
<scope>provided</scope> <!-- Not provided by Tomcat/Jetty! -->
</dependency>
Correct: Use compile (default) scope for application dependencies
<!-- GOOD — compile scope includes the JAR in the packaged artifact [src4, src5] -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>33.0.0-jre</version>
<!-- scope defaults to 'compile' — included in WEB-INF/lib and fat JARs -->
</dependency>
<!-- Only use 'provided' for APIs truly supplied by the container (servlet-api, etc.) -->
Common Pitfalls
- ClassNotFoundException vs NoClassDefFoundError confusion:
ClassNotFoundExceptionoccurs during explicit dynamic loading (Class.forName());NoClassDefFoundErroroccurs when the JVM itself tries to resolve a compile-time class reference. Different causes, different handling. [src3, src6] - Maven scope
providedvscompile: Dependencies with<scope>provided</scope>are excluded from the packaged artifact. If the runtime container does not supply the dependency, you getClassNotFoundException. Only useprovidedfor APIs the container truly supplies (e.g.,javax.servlet-api). [src4] - Lost root cause in static initializer failures: The first load of a class with a
failing static block throws
ExceptionInInitializerErrorwith the real exception. Every subsequent attempt throwsNoClassDefFoundErrorwith no cause. Find the first error in logs. [src3, src5] - Gradle
implementationvsapiscope:implementationdependencies are not exposed to downstream consumers. If module B depends on module A which declares library X asimplementation, B cannot see X's classes, leading toNoClassDefFoundError. Useapifor exposed classes. [src5] - Fat JAR service file merging: When using
maven-shade-pluginorshadowJar, SPI service files (META-INF/services/*) from multiple JARs can overwrite each other. This breaks JDBC driver auto-registration, logging backends, etc. EnableServicesResourceTransformer(shade) ormergeServiceFiles()(shadow). [src5, src7] - IDE classpath differs from build tool classpath: A project may compile fine in
IntelliJ/Eclipse but fail at runtime because the IDE resolves dependencies differently than
mvn packageorgradle build. Always verify with the build tool's classpath. [src4] - Tomcat classloader delegation order: Tomcat loads application classes
(
WEB-INF/classes,WEB-INF/lib) before delegating to the parent. Placing a dependency in$CATALINA_HOME/libinstead ofWEB-INF/libcan cause version conflicts orClassNotFoundException. [src4]
Diagnostic Commands
# === Trace class loading at runtime ===
java -verbose:class -cp "lib/*:myapp.jar" com.example.Main 2>&1 | grep "MyMissingClass"
# Shows: [Loaded com.example.MyMissingClass from file:/path/to/library.jar]
# === Print the effective classpath ===
# Maven:
mvn dependency:build-classpath -Dmdep.outputFile=classpath.txt
cat classpath.txt
# Gradle:
./gradlew dependencies --configuration runtimeClasspath
# === Search for a class in all JARs ===
for jar in lib/*.jar; do
jar tf "$jar" | grep -q "com/example/MyClass.class" && echo "FOUND in: $jar"
done
# === Check dependency tree for conflicts ===
mvn dependency:tree -Dverbose # Maven (verbose, shows conflict resolution)
mvn dependency:tree -Dincludes=com.google.guava # Maven (filter to specific dependency)
./gradlew dependencyInsight --dependency guava # Gradle (detailed resolution path)
# === Inspect a JAR's contents ===
jar tf target/myapp.jar | head -50
jar tf target/myapp.jar | grep "com/example/"
# === Check MANIFEST.MF for Class-Path entries ===
unzip -p target/myapp.jar META-INF/MANIFEST.MF
# === Check SPI service files (JDBC drivers, etc.) ===
jar tf target/myapp.jar | grep "META-INF/services"
unzip -p target/myapp.jar META-INF/services/java.sql.Driver
# === Java 9+ module system diagnostics ===
java --show-module-resolution -m com.example.myapp/com.example.Main
java --describe-module com.example.myapp
Version History & Compatibility
| Java Version | ClassLoader/Class Changes | Impact on ClassNotFoundException/NoClassDefFoundError |
|---|---|---|
| Java 1.0–7 | Single classpath, flat classloader | Standard classpath rules; -cp or CLASSPATH env var |
| Java 8 | Last version without module system | Most guides and Stack Overflow answers target this version |
| Java 9 | Module system (JPMS) introduced | Classes in non-exported packages are invisible across modules; --add-opens/--add-exports workaround |
| Java 11 (LTS) | javax.* packages removed from JDK |
ClassNotFoundException for javax.xml.bind.*, javax.annotation.* if not added as dependencies |
| Java 16 | Strong encapsulation of JDK internals | Reflection on JDK internal classes throws IllegalAccessError; use --add-opens |
| Java 17 (LTS) | Strongly encapsulated internals; no --illegal-access=permit |
Must use explicit --add-opens for each internal package |
| Java 21 (LTS) | Virtual threads; no new classloading changes | Same classloading behavior as Java 17 |
When to Use / When Not to Use
| Use This Guide When | Don't Use When | Use Instead |
|---|---|---|
java.lang.ClassNotFoundException in stack trace |
java.lang.ClassCastException (class exists, wrong type) |
Check for duplicate JARs from different classloaders |
java.lang.NoClassDefFoundError in stack trace |
java.lang.UnsupportedClassVersionError (wrong JDK version) |
Recompile with target JDK or upgrade runtime |
ExceptionInInitializerError followed by NoClassDefFoundError |
java.lang.OutOfMemoryError: Metaspace (too many classes) |
Java OutOfMemoryError guide |
| Class works in IDE but fails in packaged JAR | IncompatibleClassChangeError (method signature changed) |
Rebuild all JARs against same library version |
JDBC ClassNotFoundException: com.mysql.cj.jdbc.Driver |
java.sql.SQLException: No suitable driver (URL wrong) |
Check JDBC URL format matches driver |
Important Caveats
- NoClassDefFoundError is permanent per JVM session: Once the JVM fails to load a class (whether due to missing JAR or failed static initializer), it caches the failure. No amount of retrying in the same JVM process will succeed — you must fix the issue and restart. [src2, src3]
- Error message format differs:
ClassNotFoundExceptionuses dots (com.example.MyClass);NoClassDefFoundErroruses slashes (com/example/MyClass). CNFE reports the name as passed toClass.forName(); NCDFE reports the JVM internal name. [src6] - Application server classloading is non-standard: Each application server (Tomcat, WildFly, WebSphere, WebLogic) implements its own classloader hierarchy with different parent-first/child-first defaults. Fixes for standalone Java may not work in a container. [src4, src5]
- Java 11+ removed Java EE modules from the JDK: Upgrading from Java 8/9/10 causes
ClassNotFoundExceptionforjavax.xml.bind.JAXBContext,javax.annotation.PostConstruct, etc. unless you add Jakarta replacements as explicit dependencies. [src1]