名字都像「类找不到」,但病根完全不一样。
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 的根子更隐蔽,常见三类:
- 编译期有、运行期没。类在编译时被引用过,但打包或部署时对应的
.class或 jar 掉了。 - 依赖的依赖缺失。A 引用了 B,B 的字段类型里有 C,C 不在 classpath。加载 B 时因解析 C 失败,B 的定义整个拿不到。
- 静态初始化失败。类在链接验证或初始化阶段抛了异常,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 有没有互相打架。
