StackTraces
Java已验证

Java VerifyError 与 ClassFormatError 的区别

@debug_master32 天前更新阅读 6 min00

这类错几乎跟你写的业务代码无关:JVM 在真正执行一个类之前,会先校验字节码是否合法、类型是否符合安全规则。一旦某个 .class 是坏的、版本不匹配、或被 ASM/Lombok 等工具改坏,就会在校验阶段崩掉。报错里的类名是唯一的线索——它分不清问题在你的代码还是库里,但能告诉你先去查谁。

Issue & Env(案发现场与环境)

  • 现象:启动时或首次加载某类时抛 VerifyError、ClassFormatError,或编译/运行时报 Unsupported class file major version。
  • 环境:常出现在升级 JDK、引入字节码增强(ASM / Javassist / ByteBuddy / Lombok / AOP)或依赖冲突之后。

StackTrace(堆栈日志)

典型的校验失败:

java.lang.VerifyError: Stack map does not match the one at exception handler
	at org.springframework.cglib.proxy.Enhancer.setThreadCallbacks(Enhancer.java:158)

版本不匹配的线索在报错文案里:

java.lang.IllegalArgumentException: Unsupported class file major version 69
    at org.lombokweb.asm.ClassReader.<init>(ClassReader.java:200)
    at org.lombokweb.asm.ClassReader.<init>(ClassReader.java:187)
    at lombok.javac.handlers.HandleSneakyThrows.compile(HandleSneakyThrows.java:240)
    at lombok.javac.HandlerLibrary.handleAnnotation(HandlerLibrary.java:250)
    at lombok.javac.JavacAnnotationHandler.handle(JavacAnnotationHandler.java:157)
    at com.sun.tools.javac.main.JavaCompiler.processAnnotations(JavaCompiler.java:1270)

Reproduce(复现代码)

把类声明成 final,又让 Spring 用 CGLIB 去给它做代理:

@Service
public final class LogService {   // final 无法被 CGLIB 继承
    public void log(String msg) {
        System.out.println(msg);
    }
}

或用一份老版本 Lombok 去编译高版本 JDK 的代码,@SneakyThrows 处理阶段在读取 class 时直接抛版本不支持。

Root Cause(根因分析)

这两兄弟的分工:ClassFormatError 是 .class 文件结构层面就坏,连解析都过不去;VerifyError 是结构能解析,但里面的字节码违反了 Java 的安全规则(栈类型对不上、跳转目标非法等)。它们都是 JVM 在「加载 → 校验」这步拦下来的,属于 JVM 级 ERROR,不是普通 Exception。

来源集中在四类:

  1. 字节码增强工具生成了非法字节码——ASM、Javassist、ByteBuddy 用错 API,或生成的栈映射(StackMapTable)和服务端字节码不一致。
  2. 编译产物版本高于运行 JVM——JDK 21 编译的 class 在 JDK 8 上跑,class file major version 对不上。
  3. Lombok 等代码生成器与 JDK 版本不兼容——JDK 21 需要 Lombok 1.18.30 以上,老版本读不了新 class 结构。
  4. ASM / CGLIB 等库在依赖树里出现多个版本,Spring 用的 A 版本,字节码却按 B 版本生成,上下对不齐。

final 类被 CGLIB 代理失败这类,本质也是字节码生成层拿不到可继承的父类,最终以校验或生成异常的形式暴露出来。

The Fix(解决方案)

有一条主线:先看报错里的类名和具体信息,判断问题是「版本错位」还是「字节码坏」。

拿不准是哪个 JAR 冲突,先看依赖树:

mvn dependency:tree -Dverbose

版本不匹配就对齐:升级 Lombok(JDK 21 至少 1.18.30)或对应字节码库到与新 JDK 兼容的版本;把 class file 编译目标降到运行 JVM 支持的 --release;ASM/CGLIB 混版本就统一到 Spring 所用的那个。

确定是字节码坏,ASM 官方提供了一个校验器,能精确指到是哪条指令出了错:

java -cp "asm.jar:asm-tree.jar:asm-analysis.jar:asm-util.jar" \
    org.objectweb.asm.util.CheckClassAdapter \
    org/domain/package/YourClass.class

输出会像 Error at instruction 2: Argument 1: expected Ljava/lang/String;, but found I 这样,直接定位到出问题的指令。最后,无论哪种原因,改完都做一次干净构建 mvn clean package(或删 target / build 重编),把旧的、可能还引用老字节码的缓存清掉,避免「改完还报一样的错」。

References(参考来源)

最后更新于 2026-09-09

这篇帮到你了吗?

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

贡献一条解法