不能跨LTS版本直接升级,因字节码版本、运行时行为(如JDK11废除javax.xml.bind、JDK17禁用非法反射)、API兼容性及工具链默认行为存在实质性差异,易致UnsupportedClassVersionError或运行时崩溃。

要分阶段升级,跳版本(比如从 JDK 8 直升 JDK 17)大概率触发 UnsupportedClassVersionError 或编译失败,不是“能不能跑”,而是“哪些地方会悄无声息地坏掉”。
为什么不能跨 LTS 版本直接升级
Java 的字节码版本和运行时行为在 LTS 之间有实质性变化,不是加几个新 API 那么简单。JDK 11 废除了 javax.xml.bind,JDK 17 默认禁用 --illegal-access=deny,JDK 21 引入虚拟线程并调整了 GC 默认策略。这些都不是编译期警告,而是运行时崩溃或性能陡降。
- 编译器层面:
javac在 JDK 17 下默认生成 class 文件版本 61(对应 Java 17),老 JVM(如 JDK 8)根本无法加载 - 运行时层面:Spring Boot 2.7 要求 JDK 8–17,但 Spring Boot 3.0 明确要求 JDK 17+,且内部大量使用
sealed、record等语法特性 - 工具链层面:Maven 的
maven-compiler-plugin若未显式配置source/target或--release,会默认用当前 JDK 版本编译,导致产物不可移植
分阶段升级的关键动作
真正的“分阶段”不是装完 JDK 就改 JAVA_HOME,而是每一步都验证闭环。重点不在换版本,而在确认旧代码在新环境中是否还“认得清自己”。
- 先用
--release编译:比如在 JDK 17 环境下编译 JDK 11 兼容代码,执行javac --release 11 Main.java,避免意外引入高版本 API - 逐个替换构建插件:把
maven-compiler-plugin的source和target从1.8改成11,再测;等全通了,再改成17 - 检查依赖的二进制兼容性:运行
mvn dependency:tree -Dincludes=org.springframework,确认 Spring Framework 版本支持目标 JDK(例如 Spring 5.3.x 支持 JDK 17,但 Spring 4.3.x 不支持) - 禁用隐式反射调用:在启动参数里加
--illegal-access=warn(JDK 11/13),观察日志里是否有大量WARNING: Illegal reflective access,这是升级到 JDK 17 前必须清理的债
最容易被忽略的三个断点
很多团队卡在这三处,不是技术问题,是验证盲区。
-
JAVA_HOME和PATH看似配对,但 IDE(如 IDEA)可能缓存旧 JDK 路径,需手动在Project Structure → Project → Project SDK里重选,否则javac用的是新 JDK,而java运行时仍走旧版本 - Maven 的
toolchains.xml如果存在,它会覆盖JAVA_HOME,导致命令行mvn compile和 IDE 构建行为不一致 - Docker 构建中硬编码了基础镜像(如
openjdk:8-jre-slim),本地升级了,镜像没动,CI 流水线照样报错——得同步改Dockerfile和 CI 配置里的 JDK 标签
升级不是版本号的跳跃,是类加载路径、反射策略、GC 行为、甚至线程调度语义的渐进适配。跳过中间版本,等于跳过所有兼容性提示和迁移工具(如 jdeprscan、jdeps)能帮你的地方。最稳的路径,就是让每个 LTS 成为你验证过的落脚点,而不是路标。


















