StackTraces
Java已驗證

ClassNotFoundException 與 NoClassDefFoundError 的區別

@debug_master32 天前更新閱讀 6 min00

名字都像「類找不到」,但病根完全不一樣。ClassNotFoundException 是 checked exception,只在反射或 loadClass() 這類顯式按類名加載時拋,多半是 classpath 裡少了依賴。NoClassDefFoundError 是 Error,編譯期明明存在、運行期卻拿不到定義,常見於靜態初始化塊拋例外、版本衝突或部署缺包。

Issue & Env(案發現場與環境)

兩個詞都圍著 Java 的類加載轉。類不會一啟動就全被加載,而是第一次用到時才由 class loader 去 classpath 裡找 .class 文件。

「類找不到」會以兩種身份出現:顯式按名字找的時候拋 ClassNotFoundException,隱式正常執行程式碼時拋 NoClassDefFoundError。前者可恢復,後者是打破約定的錯誤。

兩者從 JDK 早期版本就存在,和具體版本無關。容易踩坑的環境有三種:

  • Maven 裡把某依賴的 <scope>provided</scope> 設錯,編譯在、運行不在。
  • web 應用(Tomcat 等)部署時,WEB-INF/lib 裡漏了 jar,或者 jar 誤入服務器公共 lib 目錄導致加載衝突。
  • NoClassDefFoundError 常見於 Jar 版本衝突:編譯時用 A 庫的 1.2,運行期 classpath 裡卻是 1.0。

排查 Maven 依賴用 mvn dependency:tree,Gradle 用 ./gradlew dependencies。

StackTrace(堆疊日誌)

ClassNotFoundException 典型報錯來自反射按名加載:

java.lang.ClassNotFoundException: com.example.DatabaseDriver
    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.Class.forName0(Native Method)
    at java.base/java.lang.Class.forName(Class.java:315)
    at com.example.DatabaseDriverLoader.load(DatabaseDriverLoader.java:22)
    at com.example.Main.main(Main.java:9)

NoClassDefFoundError 的現場不一樣。程式碼編譯時一切正常,運行期卻在 new MyService() 或方法調用處崩掉:

Exception in thread "main" java.lang.NoClassDefFoundError: com/example/MyService
    at com.example.Main.run(Main.java:10)
    at com.example.Main.main(Main.java:5)
Caused by: java.lang.ClassNotFoundException: com.example.MyService
    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.Class.forName0(Native Method)
    at java.base/java.lang.Class.forName(Class.java:315)

注意兩點:一是 Caused by 裡常跟著一個 ClassNotFoundException,很多人看到它就去補依賴,結果方向跑偏;二是它發生在「已編譯通過」之後,說明不是缺源碼,而是運行環境與編譯環境不一致。

Reproduce(復現程式碼)

ClassNotFoundException 的最小復現,一行反射即可:

public class Main {
    public static void main(String[] args) {
        try {
            Class.forName("com.example.NotExist");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

NoClassDefFoundError 經典復現是靜態初始化塊拋例外。先把類加載失敗,之後每次引用都報 NoClassDefFoundError:

public class Foo {
    static {
        int x = 1 / 0; // 静态初始化抛 ArithmeticException
    }
    public static void hello() {}
}

public class Main {
    public static void main(String[] args) {
        Foo.hello();
    }
}

首輪加載拋的是 ExceptionInInitializerError,之後 JVM 記住「這個類定義不可用」,再觸發就報 NoClassDefFoundError。

Root Cause(根因分析)

ClassNotFoundException 的根子在類加載環節。Class.forName() 和 ClassLoader.loadClass() 按字符串名定位類,靠 classpath 一層層找。名字錯了、jar 少了、路徑寫岔了,就直接拋這個檢查型例外。因為是顯式請求,語義上「允許類不存在」,編譯期不校驗,出問題在運行期暴露。

NoClassDefFoundError 的根子更隱蔽,常見三類:

  1. 編譯期有、運行期沒。類在編譯時被引用過,但打包或部署時對應的 .class 或 jar 掉了。
  2. 依賴的依賴缺失。A 引用了 B,B 的字段類型裡有 C,C 不在 classpath。加載 B 時因解析 C 失敗,B 的定義整個拿不到。
  3. 靜態初始化失敗。類在連結驗證或初始化階段拋了例外,JVM 標記該類定義不可用,後續引用統一報 NoClassDefFoundError。

Stack Overflow 的一條高票回答把界線劃得清楚:ClassNotFoundException 是「你想加載一個可能不存在的類」,NoClassDefFoundError 是「一個本該存在的類定義沒了」。一個是 Exception,處理它;一個是 Error,多半是環境壞了。

The Fix(解決方案)

ClassNotFoundException 先補依賴、對準名字。命令行用 jar tf 驗證某個類是否打包進去:

jar tf your-library.jar | grep com/example/DatabaseDriver

Maven 項目重點查 provided 作用域,它不進最終產物。類名拼寫、包路徑大小寫都要核對一遍。

NoClassDefFoundError 是被動結果,真正的線索在堆疊最前面幾行,尤其帶 Caused by 的原始例外或 ExceptionInInitializerError。先解決那個根因,別急著補 jar。

mvn dependency:tree
# 或
./gradlew dependencies

找到版本衝突就鎖定一個版本、排除多餘依賴。靜態初始化失敗的情況,把 static 塊裡可能拋例外的程式碼挪到需要時才執行,或包一層 try-catch。最後核對部署產物:war/jar 裡是否真帶上那個類,服務器公共 lib 與 .war 內 lib 有沒有互相打架。

References(參考來源)

最後更新於 2026-09-09

這篇幫到你了嗎?

剛解決了一個棘手的報錯?花兩分鐘記錄下來,幫助下一個遇到同樣問題的開發者。

貢獻一條解法