> ## Content Index
> Fetch the complete content index at: https://www.ggorantala.dev/llms.txt
> Use this file to discover other available public pages before exploring further.

# NoClassDefFoundError vs ClassNotFoundException in Java
- URL: https://www.ggorantala.dev/ava-noclassdeffounderror-vs-classnotfoundexception/
- Published: 2026-09-01T05:56:18.000Z
- Updated: 2026-09-01T05:59:55.000Z
- Description: NoClassDefFoundError and ClassNotFoundException look interchangeable and are not. Read the frames, find the real cause, and stop the repeat throws.
- Author: Gopi Gorantala
- Tags: Java, java-errors, noclassdeffounderror, advertised-listeners, jvm, class-loading, core-java, java-17

## 1\. The Error

Three different failures print under these two names. The message and the **top frame** tell you which one you have.

**A — a class your code references is not on the classpath.** The `NoClassDefFoundError` is at *your* frame, the cause is a `ClassNotFoundException`, and the name is slash-separated in the error and dot-separated in the cause:

```log
Exception in thread "main" java.lang.NoClassDefFoundError: lib/Helper
	at app.Main.main(Main.java:5)
Caused by: java.lang.ClassNotFoundException: lib.Helper
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:526)
	... 1 more
```

**B — a *supertype* (or another class needed to define the class you asked for) is missing.** Same error name, completely different frames: the throw site is `ClassLoader.defineClass1`, a native method. If you see `defineClass1` at the top, the class named in the message is **not** the class you referenced — it is something that class needs in order to exist:

```log
Exception in thread "main" java.lang.NoClassDefFoundError: base/Base
	at java.base/java.lang.ClassLoader.defineClass1(Native Method)
	at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1027)
	at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:150)
	at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:862)
	at java.base/jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(BuiltinClassLoader.java:760)
	...
	at app.Boot.main(Boot.java:4)
Caused by: java.lang.ClassNotFoundException: base.Base
```

**C — the class is present, but its static initializer already blew up.** This is the one that wastes hours, because the message names a class that is sitting right there in the jar:

```log
java.lang.NoClassDefFoundError: Could not initialize class Config
	at InitRepro.main(InitRepro.java:5)
Caused by: java.lang.ExceptionInInitializerError: Exception java.lang.NullPointerException [in thread "main"]
	at Config.<clinit>(Config.java:4)
	... 1 more
```

That `Caused by` line **only exists on JDK 17 and later.** On 8u502 and 11.0.32 the same failure prints with no cause at all:

```log
java.lang.NoClassDefFoundError: Could not initialize class Config
	at InitRepro.main(InitRepro.java:5)
```

And a plain `ClassNotFoundException` — with no `NoClassDefFoundError` wrapper — means someone asked for the class *by name*, through `Class.forName`, `ClassLoader.loadClass`, a `ServiceLoader`, or a reflective framework:

```log
java.lang.ClassNotFoundException: lib.Absent
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
	at java.base/java.lang.Class.forName0(Native Method)
	at java.base/java.lang.Class.forName(Class.java:423)
	at java.base/java.lang.Class.forName(Class.java:414)
	at Repro3.main(Repro3.java:4)
```

All traces above were produced on OpenJDK 21.0.10; frame line numbers differ per release (see §3).

## 2\. How to Reproduce It

**A/B — missing class and missing supertype.** Two source files, then delete one class file:

```java
// lib/Helper.java
package lib;

public class Helper { 
    public static String greet() { 
        return "hi"; 
    } 
}
```

```java
// app/Main.java
package app;

import lib.Helper;

public class Main {
    public static void main(String[] args) {
        System.out.println(Helper.greet()); 
    }
}
```

```bash
javac -d out lib/Helper.java app/Main.java
rm out/lib/Helper.class          # compiles against it, runs without it
java -cp out app.Main            # -> NoClassDefFoundError: lib/Helper
```

For variant B, make `app.Child extends base.Base`, then `rm -rf out/base` and instantiate `Child`. The error names `base/Base`, thrown from `defineClass1`.

**C — erroneous initialization state.** The point of the loop is that the *first* throw and the *second* throw are different exceptions:

```java
// Config.java
public class Config {
    static final String URL;
    static { 
        URL = System.getProperty("db.url").trim(); 
    }   // NPE when -Ddb.url is absent
    static String url() { return URL; }
}
```

```java
// InitRepro.java
public class InitRepro {
    public static void main(String[] args) {
        for (int attempt = 1; attempt <= 2; attempt++) {
            try { 
                System.out.println("attempt " + attempt + " -> " + Config.url()); 
            }
            catch (Throwable t) { 
                System.out.println("attempt " + attempt + " threw:"); t.printStackTrace(System.out); 
            }
        }
    }
}
```

```bash
javac -d out Config.java InitRepro.java
java -cp out InitRepro           # attempt 1: ExceptionInInitializerError
                                 # attempt 2: NoClassDefFoundError: Could not initialize class Config
```

Environment triggers that make this production-only: a config property or environment variable set in dev and missing in the target namespace; a static block that reads a file, opens a socket, or loads a native library; a shaded jar whose relocation dropped a transitive dependency; two versions of the same artifact where the winner lacks a class the loser had.

## 3\. Version Behaviour Matrix

| Behaviour                                                                         | 8u502                                                                           | 11.0.32                                    | 17.0.20                         | 21.0.10    | 25         |
| --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------ | ------------------------------- | ---------- | ---------- |
| NoClassDefFoundError \+ Caused by: ClassNotFoundException on **first** resolution | yes                                                                             | yes                                        | yes                             | yes        | yes        |
| Cause preserved on the **second and later** throws for the same reference         | **no** (cause=null)                                                             | **no** (cause=null)                        | yes                             | yes        | yes        |
| Could not initialize class X carries Caused by: ExceptionInInitializerError       | **no**                                                                          | **no**                                     | yes                             | yes        | yes        |
| Loader frames                                                                     | sun.misc.Launcher$AppClassLoader, URLClassLoader, AccessController.doPrivileged | jdk.internal.loader.BuiltinClassLoader:581 | :641                            | :641       | :641       |
| wrong name message argument order                                                 | launcher prints no cause                                                        | lib/Helper (wrong name: Helper)            | Helper (wrong name: lib/Helper) | same as 17 | same as 17 |
| javax.xml.bind.DatatypeConverter resolvable                                       | yes                                                                             | **no** (JEP 320)                           | no                              | no         | no         |
| \-Xlog:class+load, class+init, class+resolve                                      | no (\-verbose:class only)                                                       | yes                                        | yes                             | yes        | yes        |

*The 8, 11, 17 and 21 columns were produced locally on OpenJDK 8u502, 11.0.32, 17.0.20 and 21.0.10\. The 25 column reflects no behaviour change in these areas relative to 21.*

The cause-preservation change is [JDK-8048190, *"NoClassDefFoundError omits original ExceptionInInitializerError"*](https://bugs.openjdk.org/browse/JDK-8048190), integrated August 2021 and present in the 17 update train; it is still absent on 8u502 and 11.0.32\. The `wrong name` argument order is reversed on 11 relative to 17+ — if you are reading an old ticket, check which JDK produced it before believing which name is the file's and which is the request's.

## 4\. Why It Happens — Surface Level

`ClassNotFoundException` is a **checked exception thrown by a class loader** when it was asked for a name and could not find bytes for it. Somebody called `Class.forName`, `loadClass`, or `ServiceLoader.load`. It is a normal, catchable outcome — frameworks throw and swallow it constantly while probing for optional dependencies.

`NoClassDefFoundError` is an **Error thrown by the JVM** while resolving a symbolic reference that `javac` already validated. It means: this code was compiled against a class that is not here now, or is here but is unusable. You do not get to "handle" it; it is a deployment defect. The `Could not initialize class` variant is a third thing again — the class was found and defined, but its `<clinit>` threw, and the JVM has permanently marked it unusable.

## 5\. Why It Happens — Under the Hood

Class creation is three phases (JVMS §5.3 loading, §5.4 linking, §5.5 initialization), and each phase has its own failure signature.

**Loading and delegation.** `loadClass` delegates to the parent loader first, then calls `findClass`. When no loader in the chain produces bytes, the last one throws `ClassNotFoundException`. On JDK 8 that path is `URLClassLoader.findClass` → `AccessController.doPrivileged`; from 9 onward it is `jdk.internal.loader.BuiltinClassLoader.loadClass` → `findClassOnClassPathOrNull`. The `doPrivileged` frame disappearing from modern traces is the visible edge of the Security Manager's long retirement (permanently disabled by JEP 486 in JDK 24).

**Resolution wrapping.** When the JVM resolves a `CONSTANT_Class_info` entry from your class's constant pool and the loader throws `ClassNotFoundException`, JVMS §5.4.3.1 requires it to be reported as `NoClassDefFoundError` — hence the slash-separated internal name in the error (it comes from the constant pool) and the dot-separated binary name in the cause (it comes from the loader API). Resolution failures are cached in HotSpot's resolution-error table so the second execution of the same bytecode does not re-run the search. On 8 and 11 that cache stores the error class and message but not the causing throwable, which is exactly why `getCause()` returns `null` from the second throw onward — verified on both. From 17 the cause survives.

**defineClass and the supertype.** `defineClass1` is where the verifier and the class-file parser run. Defining `Child` requires its superclass to be loaded first, so the failure surfaces *inside* the definition of a class that is present, naming a class that is not. That is why variant B's message names a class your code may never mention.

**Erroneous state.** JVMS §5.5 says that if `<clinit>` completes abruptly, the JVM marks the class **erroneous**, and every later initialization attempt throws `NoClassDefFoundError` without re-running `<clinit>`. There is no retry, no reset, and no way back short of a new class loader. JDK-8048190 added a fixed-size table that stores the original `ExceptionInInitializerError` so it can be attached as the cause; its synthesised message is visible verbatim in the trace and in JFR:

```log
message = "Exception java.lang.NullPointerException [in thread "main"]"
thrownClass = java.lang.ExceptionInInitializerError
```

Note that `Class.forName(name, false, loader)` loads without initializing, so it succeeds against a class whose `<clinit>` would throw — verified on 8 and 21\. `Class.forName(name)` initializes, and throws.

## 6\. The Fix

**Variant A/B — find who is missing and why.** Do not guess from the message; log the resolution:

```bash
# JDK 9+: what got loaded, from which jar, and who referenced it
java -Xlog:class+load=info -cp app.jar app.Main | grep Helper
java -Xlog:class+resolve=debug -cp app.jar app.Main | grep Helper
# JDK 8:
java -verbose:class -cp app.jar app.Main | grep Helper
```

`class+resolve` prints the referencing class and source line, e.g. `InitRepro Config InitRepro.java:5 (explicit)` — that is the reference you need to satisfy. Then confirm the artifact situation:

```bash
mvn dependency:tree -Dincludes=:::                 # or: gradle dependencies --configuration runtimeClasspath
for j in lib/*.jar; do unzip -l "$j" | grep -q 'lib/Helper.class' && echo "$j"; done
```

Two hits means a duplicate-class conflict; zero means a `provided`/`compileOnly` scope that never made it to runtime.

**Variant C — stop putting failable work in `<clinit>`.**

```diff
-public class Config {
-    static final String URL;
-    static { URL = System.getProperty("db.url").trim(); }
-    static String url() { return URL; }
-}
+public final class DbConfig {
+    private final String url;
+    private DbConfig(String url) { this.url = url; }
+
+    public static DbConfig fromSystemProperties() {
+        String url = System.getProperty("db.url");
+        if (url == null || url.trim().isEmpty()) {
+            throw new IllegalStateException(
+                "db.url is not set; start the JVM with -Ddb.url=jdbc:postgresql://host:5432/db");
+        }
+        return new DbConfig(url.trim());
+    }
+
+    public String url() { return url; }
+}
```

Every call now fails identically, with a message that names the missing property. Verified on 8u502, 11.0.32, 17.0.20 and 21.0.10: both attempts print the same `IllegalStateException`.

**When you cannot drop the static field** — legacy API, generated code, a public constant others compile against — make `<clinit>` incapable of throwing and rethrow from the accessor. This restores the cause on JDK 8 and 11, where the JVM will not:

```java
public final class LegacyConfig {
    private static final String URL;
    private static final RuntimeException FAILURE;

    static {
        String url = null;
        RuntimeException failure = null;
        try {
            url = System.getProperty("db.url").trim();
        } catch (RuntimeException e) {
            failure = new IllegalStateException("db.url is not set", e);
        }
        URL = url;
        FAILURE = failure;
    }

    private LegacyConfig() { }

    public static String url() {
        if (FAILURE != null) throw FAILURE;
        return URL;
    }
}
```

Verified on all four JDKs: attempts 1 and 2 both report `IllegalStateException: db.url is not set | cause=java.lang.NullPointerException`.

## 7\. Best Practices & The Better Design

- **`<clinit>` should only build constants.** No `System.getProperty`, no file reads, no `Class.forName`, no `System.loadLibrary`. Anything that can fail belongs in a factory method that fails the same way every time.
- **Never catch `ClassNotFoundException` and continue silently.** If you are probing for an optional dependency, use `Class.forName(name, false, loader)` so a present-but-broken class does not initialize during a capability check, and record which branch you took.
- **One version of every artifact.** Enforce it (`maven-enforcer-plugin`'s `dependencyConvergence`, plus `banDuplicateClasses` from `extra-enforcer-rules`) rather than discovering it from a `NoClassDefFoundError` at 3am.
- **Prefer a real dependency to reflection.** Reflection turns a compile error into a runtime `ClassNotFoundException` that will not surface until that code path runs in production.
- **`NoClassDefFoundError` is an `Error`, not an `Exception`.** A `catch (Exception e)` around your task body will not see it; in a thread pool it surfaces later as an `ExecutionException`, and in a `static` singleton it poisons the class for the life of the loader.
- **On 8/11, treat the first occurrence in the log as the only useful one.** Grep for the earliest timestamp, not the loudest stack.

Related failure modes worth linking from here: `UnsupportedClassVersionError` sits in the same linkage family but fails at the class-file header rather than at name resolution; `NoSuchMethodError` is the same story one level down, at method resolution instead of class resolution; and `InaccessibleObjectException` is what replaced "class not found" as the dominant reflection failure once JPMS closed `java.base`.

## 8\. How to Prevent It Long-Term

**JFR, and one detail that matters:** `NoClassDefFoundError` and `ExceptionInInitializerError` are `Error`s, so they are recorded by `jdk.JavaErrorThrow`, which is **enabled by default under `settings=profile`**. `ClassNotFoundException` is an `Exception`, so it lands in `jdk.JavaExceptionThrow` instead — which is **off even under `settings=profile`** and must be enabled explicitly. Verified on 21.0.10:

bash

```bash
java -XX:StartFlightRecording=filename=app.jfr,settings=profile,\
+jdk.JavaExceptionThrow#enabled=true,+jdk.JavaExceptionThrow#stackTrace=true \
     -cp app.jar app.Main
jfr summary app.jfr
jfr print --events jdk.JavaErrorThrow,jdk.JavaExceptionThrow app.jfr
```

- **Startup smoke test in CI** that boots the app with the production classpath and asserts a clean start — most `NoClassDefFoundError`s are a packaging defect and appear in the first second.
- **`jdeps --jdk-internals`** and `**jdeprscan**` in the upgrade pipeline to catch code that will lose its classes on the next LTS (`javax.xml.bind`, `javax.annotation`, `sun.*`).
- `**mvn dependency:analyze**` for `used undeclared` dependencies — the classic source of "works on my machine, `NoClassDefFoundError` in the shaded jar".
- **Boot-time config validation.** Read and validate every property in one place at startup and fail with a list, not one `<clinit>` at a time.
- **Alert on `Could not initialize class` specifically.** It is a distinct symptom: it means the pod is up, healthy to the liveness probe, and will fail every request touching that class until it is restarted.

## 9\. Key Takeaways

- `ClassNotFoundException` \= someone asked by name and the loader had nothing. `NoClassDefFoundError` \= the JVM tried to resolve a reference `javac` had already accepted.
- Read the top frame. Your own frame → the named class is missing. `defineClass1` → a *supertype* of a class you touched is missing. `Could not initialize class` → the class is there and permanently poisoned.
- On JDK 8 and 11 only the **first** occurrence carries the cause; every later one is bare. On 17+ the cause survives (JDK-8048190).
- A class whose `<clinit>` throws is marked erroneous for the life of its class loader. There is no retry.
- Keep static initializers to constants; put anything that can fail behind a factory method that fails identically every time.

## 10\. Related Questions

### What causes and what are the differences between NoClassDefFoundError and ClassNotFoundException?

`ClassNotFoundException` is a checked exception thrown by a class loader when an explicit by-name lookup (`Class.forName`, `loadClass`, `ServiceLoader`) finds no bytes. `NoClassDefFoundError` is an `Error` thrown by the JVM when resolving a constant-pool reference that compiled fine. The second usually wraps the first as its cause; when it does not, the class exists and something else is wrong with it.

### What causes "java.lang.NoClassDefFoundError: Could not initialize class"?

The class was loaded successfully, but its static initializer threw. JVMS §5.5 marks such a class erroneous, and every later initialization attempt throws this error without re-running `<clinit>`. The real failure is in the first `ExceptionInInitializerError`, which appears only once. On JDK 17+ it is attached as the cause of every repeat; on 8 and 11 it is not.

### Why is the class name written with slashes in NoClassDefFoundError?

Because the JVM reports it from the constant pool, which stores internal names (`lib/Helper`) rather than binary names (`lib.Helper`). The `ClassNotFoundException` in the cause comes from the class-loader API, which uses binary names — so the same class appears in two spellings in one stack trace.

### Can I catch NoClassDefFoundError and recover?

You can catch it — it is a `Throwable` — but recovery is almost never possible. For a missing class the deployment is wrong. For an erroneous class the JVM will keep throwing until a new class loader defines it again. Catching is defensible only for genuinely optional integrations, and then `Class.forName(name, false, loader)` is the honest way to ask.

### Why does my code fail with ClassNotFoundException on Java 11 but work on Java 8?

The most likely answer is JEP 320, which removed the Java EE and CORBA modules in Java 11 — `javax.xml.bind.DatatypeConverter`, `javax.annotation.PostConstruct`, `javax.activation`. Verified: the class resolves on 8u502 and throws `ClassNotFoundException` on 11.0.32, 17.0.20 and 21.0.10\. Add the standalone artifact (`jakarta.xml.bind-api` plus an implementation) as an ordinary dependency.