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

这篇帮到你了吗?

刚解决了一个棘手的报错?花两分钟记录下来,帮助下一个遇到同样问题的开发者。

贡献一条解法