java.lang.ClassNotFoundException / java.lang.NoClassDefFoundError

Quick answer

These are two different classloading failures, not variants of the same bug. ClassNotFoundException fires when code explicitly asks to load a class by name at runtime (Class.forName, a JDBC driver, a plugin loader) and it isn't on the classpath. NoClassDefFoundError fires when the JVM implicitly needs a class your compiled code already references, and either the class file is missing at runtime (present at compile time, absent when deployed) or its static initializer already failed once earlier in this same run.

The exact error strings

// explicit, by-name load — the class genuinely isn't on the classpath:
java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver
	at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:445)
	at com.example.App.main(App.java:9)

// implicit load — bytecode already references this class:
Exception in thread "main" java.lang.NoClassDefFoundError: com/example/PaymentProcessor
	at com.example.App.main(App.java:14)
Caused by: java.lang.ClassNotFoundException: com.example.PaymentProcessor
	at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:445)
	... 1 more

Note the second example: a NoClassDefFoundError can itself be caused by a ClassNotFoundException underneath — the JVM's implicit resolution mechanism internally uses the same by-name classloading lookup, and when that lookup fails, the outer implicit-load failure surfaces as the Error, with the inner ClassNotFoundException chained as its cause. Seeing both in one trace does not mean two separate bugs; it means one missing-classpath-entry problem, surfaced through the implicit-load path.

The two-phase classloading model, visually

Compile time: class resolves fine program runs Implicit load bytecode already references the class NoClassDefFoundError not on runtime classpath, or static init failed once Explicit load Class.forName("...") by name ClassNotFoundException named class isn't on the classpath Both failures share the same underlying lookup — only the trigger (implicit vs. explicit request) differs

"Compiled fine" only proves the class existed on the build classpath at that time — it says nothing about the runtime classpath, which is checked separately, later, and can differ.

Cause 1 (NoClassDefFoundError): a jar present at compile time, missing at runtime

# build succeeds — the dependency jar is on the build tool's classpath:
mvn package
# BUILD SUCCESS

# but the deployed/runtime environment (a Docker image, a manually
# assembled classpath) never copied that jar into it:
java -cp app.jar com.example.App
# Exception in thread "main" java.lang.NoClassDefFoundError: ...

# ✅ ensure every compile-time dependency is also included in the
#    runtime artifact — e.g. a "fat jar" / shaded jar bundling all
#    dependencies, or an explicit COPY step in a Dockerfile

Compilation and execution consult two logically separate classpaths, and build tools like Maven and Gradle can make this distinction invisible during local development (where both classpaths happen to line up) while a deployment pipeline silently diverges them — a common cause is a multi-stage Docker build where the compile stage has full access to a dependency cache, but the final runtime image only copies the application's own class files and forgets to also copy the third-party jars it depends on. The class was never missing from the code; it's missing from the specific artifact that actually gets run.

Cause 2 (NoClassDefFoundError): a static initializer that already failed once

public class Config {
    static final int MAX = 100 / Integer.parseInt(System.getenv("LIMIT"));
    // ❌ throws NumberFormatException the FIRST time Config is loaded,
    //    if LIMIT isn't set or isn't a valid number
}

// first call anywhere in the program:
Config.MAX;   // throws ExceptionInInitializerError (the real root cause)

// every LATER attempt to use Config in the same JVM run:
Config.MAX;   // throws NoClassDefFoundError — Java won't retry a class
              // whose static init already failed once

The JVM guarantees a class's static initializer runs at most once per classloader per run — if that one attempt throws, the class is permanently marked as failed-to-initialize for the remainder of that JVM instance, and Java refuses to retry it, throwing NoClassDefFoundError on every subsequent reference instead. This produces a genuinely confusing trace: the exception you're staring at (NoClassDefFoundError) is not the actual bug — the real cause is an ExceptionInInitializerError from the first failed attempt, which may be much earlier in the log, or even in a different thread that happened to touch the class first.

Cause 3 (ClassNotFoundException): an explicit reflective/driver load with no matching jar

// older-style JDBC code explicitly force-loads the driver class by name:
Class.forName("com.mysql.cj.jdbc.Driver");
// ❌ java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver
//    (the mysql-connector-j jar isn't on the classpath at all)

// ✅ add the driver dependency (Maven example):
// 
//   com.mysql
//   mysql-connector-j
//   8.x
// 

This is the textbook case of the "explicit, by-name" trigger: the code itself decided, at runtime, to ask the classloader for a specific fully-qualified class name — most classically to register a JDBC driver, but the same pattern appears in plugin systems, dependency-injection frameworks scanning for implementations, and any reflection-based "load a class by string" mechanism. Because the request names the class as a plain string rather than referencing it through compiled bytecode, the compiler never gets a chance to verify the class exists at all; the failure is discovered purely at runtime, the first time that string is actually resolved.

Common variants at a glance

SituationWhich errorFix
compiled fine, fails only when deployed/runNoClassDefFoundErrorensure the runtime artifact includes every compile-time dependency
fails every time after the first attempt, in the same runNoClassDefFoundError (caused by a static init failure)find the original ExceptionInInitializerError, fix that root cause
Class.forName("..."), a JDBC driver, a plugin loaderClassNotFoundExceptionadd the missing dependency jar to the classpath
works with one dependency version, fails with anotherNoClassDefFoundErrorcheck for a version conflict via a dependency-tree command

Why this happens: Java resolves classes at two distinct times, for two distinct reasons

The compiler's job is narrower than it looks: it only verifies that every class your code references is resolvable on the compile-time classpath, and emits bytecode with symbolic references to those classes — it does not, and cannot, guarantee anything about what classpath the program will actually run against later. The JVM performs its own, separate resolution at runtime, and it does so in exactly two ways: implicitly, the moment compiled bytecode's reference to a class is first actually needed (triggering NoClassDefFoundError on failure), or explicitly, when application code deliberately asks for a class by name via reflection (triggering ClassNotFoundException on failure). Understanding which of these two paths produced your error immediately tells you where to look: an implicit failure means something about your deployment's classpath diverged from what you built against, while an explicit failure means a reflectively-requested dependency was never added to the classpath in the first place.

Debugging checklist

Frequently Asked Questions

What is the actual difference between ClassNotFoundException and NoClassDefFoundError?

ClassNotFoundException is a checked exception thrown when code explicitly asks the classloader to load a class by name at runtime (Class.forName, a JDBC driver registration, a plugin system) and that name can't be resolved on the classpath. NoClassDefFoundError is thrown when the JVM needs a class implicitly — because compiled bytecode already references it — and either can't find its .class file at runtime despite it being present at compile time, or the class's static initializer threw an exception the first time it was loaded.

Why does my code compile fine but throw NoClassDefFoundError at runtime?

Compilation only needs a class's .class file (or a .jar containing it) available on the compile-time classpath; running the program requires that same class to be present on the runtime classpath too. If a dependency jar is on the build classpath (via a build tool) but never gets copied into the deployed runtime environment — a common packaging or Docker-image mistake — the class resolves fine when compiling but is missing the moment the JVM actually needs to load it.

Can a static initializer failure cause NoClassDefFoundError?

Yes, and this is one of the most confusing variants: the JVM only attempts to run a class's static initializer once. If that first attempt throws any exception, Java marks the class as having failed initialization and throws NoClassDefFoundError on every subsequent attempt to use that class in the same JVM run — even though the class file itself is perfectly present. The real underlying cause is hidden in the ExceptionInInitializerError from the very first, original failed attempt, often much earlier in the log.

Why would a JDBC driver throw ClassNotFoundException specifically?

Older JDBC code explicitly calls Class.forName("com.mysql.cj.jdbc.Driver") to force-load and register a database driver class by its fully-qualified name before opening a connection. If that driver's jar isn't on the classpath, the explicit, by-name load fails with ClassNotFoundException — a textbook example of the "code explicitly asked for a class by name" trigger, as opposed to the JVM implicitly needing a class it already knows about from compiled bytecode.

Does a version mismatch between compile-time and runtime jars cause this?

Yes — if code compiles against version 2.x of a library (where a class exists) but the runtime classpath ends up with version 1.x of the same library (where that class doesn't exist, or exists under a different name/package), the compiled bytecode's reference to the class can't be satisfied at runtime, producing NoClassDefFoundError. This is a common symptom of a dependency-management conflict where two different versions of the same library end up on the classpath simultaneously.

How do I find which jar or module actually contains the missing class?

For a build-tool-managed project, running the dependency tree command (mvn dependency:tree or gradle dependencies) and searching the output for the expected library shows whether it's present, missing, or shadowed by a conflicting version. For a plain classpath, grepping the class name against every jar (unzip -l each jar and search for the .class file) locates exactly which jar should be providing it, or confirms it's absent from all of them.

More Go, Rust & Java errors

Browse the full reference for Go, Rust, Java, and other backend language errors — exact message, cause, and fix.

All Error References Java: error: cannot find symbol HTTP Status Codes
About the author

Pasindu Ishan is a software developer based in Sri Lanka. He builds privacy-first developer tools at JSON Dev Tools.