1. 简介

ClassNotFoundException 和 NoClassDefFoundError 都发生在 JVM 无法在类路径(classpath)中找到请求的类时。虽然看起来相似,但两者存在核心差异。

本文将探讨它们出现的原因及解决方案。

2. ClassNotFoundException

ClassNotFoundException 是一个受检异常(checked exception),当应用程序尝试通过类的完全限定名(fully-qualified name)加载类,但在类路径中找不到其定义时抛出。

这种情况主要发生在使用以下方法时:

  • Class.forName()
  • ClassLoader.loadClass()
  • ClassLoader.findSystemClass()

因此,在使用反射(reflection)时需特别注意 java.lang.ClassNotFoundException

举个典型例子:尝试加载 JDBC 驱动类但未添加必要依赖,就会抛出 ClassNotFoundException:

@Test(expected = ClassNotFoundException.class)
public void givenNoDrivers_whenLoadDriverClass_thenClassNotFoundException() 
  throws ClassNotFoundException {
      Class.forName("oracle.jdbc.driver.OracleDriver");
}

3. NoClassDefFoundError

NoClassDefFoundError 是一个致命错误(fatal error)。当 JVM 尝试以下操作时无法找到类定义就会发生:

  • 使用 new 关键字实例化类
  • 通过方法调用加载类

这个错误的特点是:编译器能成功编译类,但 Java 运行时无法定位类文件。通常发生在类的静态块(static block)或静态字段(static field)初始化时抛出异常,导致类初始化失败。

看一个简单复现场景:ClassWithInitErrors 类初始化时抛出异常。首次创建对象会得到 ExceptionInInitializerError,再次尝试加载同一类就会触发 NoClassDefFoundError

public class ClassWithInitErrors {
    static int data = 1 / 0; // 静态初始化抛出异常
}
public class NoClassDefFoundErrorExample {
    public ClassWithInitErrors getClassWithInitErrors() {
        ClassWithInitErrors test;
        try {
            test = new ClassWithInitErrors(); // 首次初始化失败
        } catch (Throwable t) {
            System.out.println(t); // 捕获 ExceptionInInitializerError
        }
        test = new ClassWithInitErrors(); // 二次加载触发 NoClassDefFoundError
        return test;
    }
}

测试用例验证:

@Test(expected = NoClassDefFoundError.class)
public void givenInitErrorInClass_whenloadClass_thenNoClassDefFoundError() {
    NoClassDefFoundErrorExample sample = new NoClassDefFoundErrorExample();
    sample.getClassWithInitErrors();
}

4. 解决方案

诊断和修复这两个问题可能很耗时,但根本原因都是运行时类路径中缺少类文件。可采取以下措施:

  1. 检查类路径
    确保类或包含该类的 jar 包在类路径中可用。若缺失需添加依赖。

  2. ⚠️ 排查类路径覆盖
    若类存在但问题依旧,可能是类路径被覆盖。需定位应用程序使用的确切类路径。

  3. 🔍 类加载器问题
    当应用使用多个类加载器时,一个类加载器加载的类可能对其他加载器不可见。深入理解 Java 类加载机制 是排查关键。

5. 总结

虽然两者都与类路径和运行时找不到类有关,但核心区别在于:

  • ClassNotFoundException:运行时动态加载类(通过字符串类名)时找不到类
  • NoClassDefFoundError:编译时类存在,但运行时因初始化失败等原因导致类不可用

完整示例代码见 GitHub 仓库


原始标题:ClassNotFoundException vs NoClassDefFoundError