Watch at Ease看见世界,读懂生活。

科技 · 实用指南

遇到 UnsupportedClassVersionError?改 Java 前先证明类文件与加载 JVM

错误已经给出兼容性不匹配的两端。先保存类文件主版本、产物和真实启动器,再决定升级运行时还是重新定向构建,清除所有陈旧输出,并在本地、CI 与生产等价入口验证同一产物。

Java 类文件与运行时边界图
原创插画:把被拒绝的产物与真正加载它的 JVM 对齐。 Watch at Ease

错误已经给出兼容性不匹配的两端。先保存类文件主版本、产物和真实启动器,再决定升级运行时还是重新定向构建,清除所有陈旧输出,并在本地、CI 与生产等价入口验证同一产物。

JVM 只能加载自己支持的类文件版本;更高主版本会在应用代码运行前被拒绝。 Java 8 到 Java 25 分别对应类文件主版本 52 到 69。

1. 冻结准确的不匹配证据

复制第一条完整 UnsupportedClassVersionError,包括被拒绝类、报告的类文件版本、运行时最大识别版本、启动命令和时间。必须在失败上下文中记录 java -version 与 javac -version,不要用之后打开的另一个 shell 代替。

用 JVM 规范换算两个主版本,但不要停在版本表。普通主版本不匹配与次版本 65535 的预览不匹配要求不同;只要错误或 javap 暴露,就同时记录主版本和次版本。

2. 识别被拒绝的产物

定位第一条栈帧点名的准确 .class 或 JAR,记录路径、加密哈希、包坐标或镜像摘要和构建来源。条件允许时对提取的类运行 javap -verbose,不执行代码即可确认主版本。

判断被拒绝类属于应用、构建插件、注解处理器、IDE 辅助程序还是传递依赖。修改应用目标不能重新定向已经发布为新版字节码的第三方插件。

3. 证明实际加载运行时

追踪真正加载该类的进程,记录解析后的 java 可执行文件、进程命令行、JAVA_HOME 或工具链来源、工作目录和容器镜像。服务检查 unit 或监督器环境,CI 检查 runner 步骤,IDE 和构建守护进程重启后再核配置。

不要把登录 shell 的 which java 当成证明。包装器、SDK 管理器、符号链接、守护进程复用、PATH 顺序和容器层都可能在 shell 看似正确时选择另一 JVM。

4. 只选择一个兼容方向

若应用确实需要新版字节码且依赖支持,就在责任启动器处升级加载运行时并核支持周期。修改服务、CI 镜像、IDE 或容器,而不只是开发 shell,并重启持久守护进程。

若必须保留旧运行时,就用 --release 或构建工具等价配置重新定向可控代码,并选择发布兼容字节码的依赖或插件版本。不要改类文件头,也不要把源码兼容误当运行时兼容。

5. 清理陈旧与混合输出

删除或隔离可能保留新版类的编译输出、缓存和打包产物。用选定工具链干净构建,记录产物哈希,并检查代表性的应用类、生成类与依赖类。

多模块混合输出很常见:某个模块、生成源码任务或插件仍可能用另一 JDK。必须证明构建日志、工具链选择和最终包,不能把编译成功当作充分证据。

6. 验证每个真实入口

让同一个产物哈希通过真正相关的 shell、IDE 或构建工具、CI、服务和生产等价容器。要求首个类可加载、预期测试通过,并且没有随后出现的其他不兼容类。

保存验收表:启动器、java 路径、运行时版本、产物哈希、类主版本、结果和回滚。只有一套文档化兼容策略同时约束构建与加载端才算完成,本地一次成功不算收口。

Java 兼容性修复矩阵
原创插画:升级运行时或重新定向构建,然后逐项验证。 Watch at Ease