bigywater.tech
常见报错 · 48

Java NoSuchMethodError、NoClassDefFoundError 与 ClassNotFoundException:方法返回类型改了源码不变也会出错,Could not initialize class 要找最早那条异常,同名类 classpath 先到先得

这三类错误都是“运行时和编译时看到的 Java 类不一样”,但修法完全不同。这篇用 JDK 21 在本机造两个版本的库,让应用对着 v1 编译、运行时换成 v2,逐个复现:缺类、static 初始化失败、方法/字段/接口变化的各种后果,以及两个 jar 里有同名类时怎么确定到底加载了谁。

7 种
库升级的二进制不兼容情形(删方法、改返回类型、改参数、删字段、static 变实例、改 private、接口加抽象方法),各抛不同的错误
void → int
ping() 返回类型改了,源码调用不用改也能编译,运行时 NoSuchMethodError
2
同名类在两个 jar 里,classpath 先出现的那个被加载

缺类

实测 · 本机执行输出
  编译期有、运行期 classpath 有 Lib(退出码 0):
      Lib v1
  运行期 classpath 去掉 Lib(直接引用 Lib.hi())(退出码 1):
      Exception in thread "main" java.lang.NoClassDefFoundError: Lib
      Caused by: java.lang.ClassNotFoundException: Lib
      	... 1 more
  两种写法同时试(退出码 0):
      捕获 ClassNotFoundException: Lib
      捕获 NoClassDefFoundError: Lib | cause = java.lang.ClassNotFoundException: Lib

ClassNotFoundException 是按名字找失败,NoClassDefFoundError 是链接时少了,后者的 cause 是前者。

static 初始化失败

实测 · 本机执行输出
  static 初始化抛异常(退出码 0):
      第 1 次调用 Lib.hi(): ExceptionInInitializerError: null | cause = java.lang.IllegalStateException: 读取配置失败:db.url 未设置
      第 2 次调用 Lib.hi(): NoClassDefFoundError: Could not initialize class Lib | cause = java.lang.ExceptionInInitializerError: Exception java.lang.IllegalStateException: 读取配置失败:db.url 未设置 [in thread "main"]
      第 3 次调用 Lib.hi(): NoClassDefFoundError: Could not initialize class Lib | cause = java.lang.ExceptionInInitializerError: Exception java.lang.IllegalStateException: 读取配置失败:db.url 未设置 [in thread "main"]

第一次是 ExceptionInInitializerError,之后是 Could not initialize class,要去找最早的那条日志。

二进制不兼容

实测 · 本机执行输出
  删除了方法 add(int,int):java.lang.NoSuchMethodError: 'int Lib.add(int, int)'
  方法 ping() 的返回类型 void→int(源码调用不用改):java.lang.NoSuchMethodError: 'void Lib.ping()'
  方法 add(int,int)→add(long,long):java.lang.NoSuchMethodError: 'int Lib.add(int, int)'
  删除了字段 sfield:java.lang.NoSuchFieldError: Class Lib does not have member field 'int sfield'
  static 方法 util() 改成实例方法:java.lang.IncompatibleClassChangeError: Expected static method 'void Lib.util()'
  方法 shout() 改成 private:java.lang.IllegalAccessError: class Main tried to access private method 'void Lib.shout()' (Main and Lib are in unnamed module of loader 'app')
  接口新增了抽象方法 b(),Impl 是对着旧接口(v1)编译的,调用方对着新接口(v2)编译:
      a() 调用成功
      java.lang.AbstractMethodError: Receiver class Impl does not define or inherit an implementation of the resolved method 'abstract void b()' of interface Svc.
  重新对着 v2 编译应用:
      <tmp>/app/Impl.java:1: error: Impl is not abstract and does not override abstract method b() in Svc

方法、字段、static 修饰、可见性和接口的改动各对应不同的 Error,报错里的签名是应用编译时看到的那个。

同名类

实测 · 本机执行输出
  classpath 顺序 v1.jar:v2.jar -> Lib 来自 v1 | 加载自 file:<tmp>/lib_v1.jar
  classpath 顺序 v2.jar:v1.jar -> Lib 来自 v2 | 加载自 file:<tmp>/lib_v2.jar
  -Xlog:class+load 里 Lib 的那一行:file:<tmp>/lib_v1.jar
  jar tf 能看到同名类:v1.jar 含 Lib.class ; v2.jar 含 Lib.class

classpath 先出现的赢,用 getCodeSource 或 -Xlog:class+load 确认来源。

分步演示

类加载
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64;报错文本都是真实复现时原样捕获的(命令行工具统一用 LC_ALL=C 取英文原文,路径换成 );所有服务都是本机上自己起的、只监听 127.0.0.1,不访问外网;计时用 time.perf_counter,数字每次运行会有小幅抖动。本篇用 Homebrew openjdk@21 21.0.13;库 Lib、接口 Svc 与应用都是实验目录里的小 Java 文件,应用对着 v1 编译、运行时换成 v2,classpath 都是目录或自己打的 jar;全部在本机,没有用 Maven / Gradle
    • 没有验证:Maven / Gradle 的依赖仲裁(mvn dependency:tree、Gradle dependencyInsight 的用法没有跑);Spring Boot fat jar 的类加载顺序;Tomcat / OSGi / 模块路径(JPMS)下的类加载隔离;ServiceLoader / SPI 找不到实现的报错(ServiceConfigurationError);NoClassDefFoundError 由类加载器不同(父子加载器)引起的情形;JDK 17 与 27 上的报错文字
    京ICP备2026067502号-1京公网安备11010502063487号