从 Java 8 到 Java 25 的迁移与 Spring 升级指南
Java 8 发布于 2014 年,距今已逾十年。尽管 Oracle 对 Java 8 的商业支持延续至 2030 年,但语言特性、运行时性能、安全补丁以及生态兼容性已显著落后于当前主流版本。Spring Boot 3.0 起将基线提升至 Java 17,Spring Framework 6 全面转向 Jakarta EE 命名空间,这意味着继续使用 Java 8 的团队将在框架升级、依赖兼容性以及云原生部署方面面临越来越高的隐性成本。
本文梳理从 Java 8 迁移到 Java 25(当前最新 LTS)的完整路径,并同步覆盖 Spring Boot 2.x 到 3.x 的升级要点。内容基于 Oracle 官方迁移指南、Spring 项目 Wiki、OpenJDK JEP 文档以及生产环境的实际迁移案例。
LTS 版本路线图与迁移节奏
Java 采用严格的半年发布周期,每三年指定一个 LTS(Long-Term Support)版本。从 Java 8 到 Java 25,历经四个 LTS 节点:
| 版本 | 发布时间 | LTS 状态 | 关键特性 |
|---|---|---|---|
| Java 8 | 2014-03 | 是 | Lambda、Stream API、新的日期时间 API |
| Java 11 | 2018-09 | 是 | 模块系统、ZGC、移除 Java EE 模块 |
| Java 17 | 2021-09 | 是 | 密封类、Records、模式匹配预览 |
| Java 21 | 2023-09 | 是 | 虚拟线程、结构化并发预览、模式匹配定版 |
| Java 25 | 2025-09 | 是 | 字符串模板预览完善、外部函数 API、JFR CPU 时间剖析 |
Oracle 对 Java 8 的 Premier Support 已于 2022 年结束,进入 Extended Support 阶段。对于仍在维护的 Java 11、17、21、25,Oracle 提供至少八年的 Premier Support。
生产环境不建议直接从 Java 8 跳至 Java 25。推荐的两阶段策略:
- 先迁移到 Java 17 + Spring Boot 2.7.x:验证兼容性,解决模块系统和依赖冲突。
- 再升级到 Java 21/25 + Spring Boot 3.x:引入虚拟线程等新特性,完成 Jakarta EE 命名空间迁移。
语言层面的核心变化
Java 9–11:模块系统与 API 收缩
Java 9 引入的 JPMS(Java Platform Module System) 是迁移中第一个需要正视的障碍。模块系统通过 module-info.java 显式声明依赖与导出包。大多数应用程序无需立即模块化,但必须处理强封装带来的反射限制和移除的 API。
Java 11 移除了六组 Java EE 和 CORBA 模块,包括 java.xml.bind(JAXB)、java.xml.ws(JAX-WS)、java.activation 等。若代码或依赖仍引用这些包,编译期即报错:
1 | |
解决路径:在 Maven/Gradle 中显式引入独立实现的依赖,如 jakarta.xml.bind:jakarta.xml.bind-api 与 org.glassfish.jaxb:jaxb-runtime。
Java 10 引入的 局部变量类型推断(var) 在 Java 11 中定型。它仅适用于局部变量,不改变运行时类型系统:
1 | |
Java 12–17:语法现代化
Java 14 引入的 Records 提供不可变数据的紧凑声明:
1 | |
Records 的设计意图是承载纯数据,而非行为。其字段隐式为 final,不可通过反射修改内部状态。
Java 17 定型的 密封类(Sealed Classes) 允许限制继承体系:
1 | |
这对框架设计和领域建模有直接影响——Spring Data 等库从 3.x 开始利用密封类优化存储库代理生成。
模式匹配(Pattern Matching) 历经多轮预览,在 Java 21 中完全定版。switch 表达式可匹配类型、解构记录并绑定变量:
1 | |
Java 21–25:并发模型与运行时革新
Java 21 的核心交付是 虚拟线程(Virtual Threads),源自 Project Loom。虚拟线程由 JVM 调度而非操作系统内核,单个线程栈仅占用数百字节到数千字节,使得百万级并发连接在常规堆内存配置下可行:
1 | |
虚拟线程与平台线程共享 java.lang.Thread API,现有同步代码无需改写即可运行。但有两类场景需要审查:
- 使用
synchronized的代码块:若虚拟线程在synchronized内执行阻塞操作(如 I/O),该虚拟线程会被固定(pinned)到平台线程,导致无法被调度器卸载。应优先使用java.util.concurrent.locks.ReentrantLock。 - ThreadLocal 的滥用:虚拟线程数量庞大时,ThreadLocal 的内存占用会被放大。Java 21 引入的
ScopedValue(孵化中)提供更有范围的值绑定机制。
Java 21 同时引入 Sequenced Collections 接口(SequencedCollection、SequencedSet、SequencedMap),为有序集合统一了 addFirst、addLast、reversed 等操作。
Java 25 作为最新 LTS,在语言层面以完善和稳定为主:字符串模板(String Templates)进入第二轮预览,JFR(JDK Flight Recorder)新增 Linux 平台的 CPU 时间剖析事件,外部函数和内存 API(Foreign Function & Memory API)持续优化 JNI 替代方案。
Spring Boot 2.x 到 3.x 的迁移
基线变化
Spring Boot 3.0 的硬性要求:
- Java 17 或更高版本,Java 8 不再受支持。
- Spring Framework 6.0,基于 Jakarta EE 9 构建。
- Servlet 6.0 / JPA 3.1,要求嵌入式容器为 Tomcat 10+ 或 Jetty 11+(Jetty 12 对应 Servlet 6)。
若项目使用 Spring Boot 1.x,必须先升级至 2.7.x,再启动向 3.x 的迁移。Spring 官方明确不支持跨大版本跳跃升级。
Jakarta EE 命名空间迁移
Spring Boot 3.0 将 Java EE 依赖全面替换为 Jakarta EE 对应版本。最直接的影响是所有 javax.* 导入需要改为 jakarta.*:
| 旧命名空间 | 新命名空间 |
|---|---|
javax.servlet.* |
jakarta.servlet.* |
javax.persistence.* |
jakarta.persistence.* |
javax.validation.* |
jakarta.validation.* |
javax.ws.rs.* |
jakarta.ws.rs.* |
javax.annotation.* |
jakarta.annotation.* |
javax.xml.bind.* |
jakarta.xml.bind.* |
这一变更并非 Spring 团队的主观决策,而是 Java EE 移交至 Eclipse Foundation 后的法定品牌迁移。javax 命名空间的所有权归属 Oracle,Eclipse Foundation 无权在新版本中使用。
对于中大型项目,手动修改数千处导入语句不可行。推荐工具:
- OpenRewrite:提供
org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0等 recipe,可自动化处理依赖升级、命名空间替换、API 迁移。Maven 插件配置示例:
1 | |
- IntelliJ IDEA:内置 Jakarta EE 迁移检查,支持一键替换项目中的
javax导入。 - Spring Boot Migrator(SBM):Spring 官方提供的实验性工具,用于分析 Spring Boot 2.x 项目并输出迁移报告。
Spring Security 6 的变化
Spring Boot 3.0 捆绑 Spring Security 6.0。Spring Security 团队为降低升级成本,提前发布了 5.8 版本作为" stepping stone":5.8 保留了所有 API,但会在使用已废弃的 javax 或旧配置方式时打印警告。推荐路径:
- 在 Spring Boot 2.7 下升级至 Spring Security 5.8。
- 根据警告信息调整安全配置。
- 升级至 Spring Boot 3.0,此时 Spring Security 自动升至 6.0。
关键变更点:
- authorizeRequests() 废弃:需替换为
authorizeHttpRequests(),后者的匹配逻辑基于AuthorizationFilter而非FilterSecurityInterceptor。 - 所有 dispatch type 默认受鉴权:Spring Security 6 对每种 Servlet dispatch type(ERROR、ASYNC、FORWARD 等)均执行授权检查。如需排除,显式配置
spring.security.filter.dispatcher-types。 - csrf().disable() 的 lambda 写法:
csrf().disable()仍可用,但推荐写法变为csrf(csrf -> csrf.disable())。
配置属性与核心行为变更
Spring Boot 3.0 引入了 spring-boot-properties-migrator 模块,可在运行时检测废弃的属性名并输出诊断信息:
1 | |
完成迁移后应移除此依赖。
其他值得注意的核心变更:
- Trailing slash 匹配默认关闭:Spring Framework 6 将
PathPatternParser设为默认,此前/path与/path/等效,现在后者返回 404。如需恢复旧行为,显式配置setUseTrailingSlashMatch(true)。 - Image banner 移除:
banner.gif、banner.jpg、banner.png不再被加载,仅支持文本banner.txt。 - Logback/Log4j2 日期格式变更:默认格式改为 ISO-8601(
yyyy-MM-dd'T'HH:mm:ss.SSSXXX),时区偏移量显式附加。 - Auto-configuration 注册方式:
spring.factories中的org.springframework.boot.autoconfigure.EnableAutoConfiguration条目不再被读取,需迁移至META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports。
Hibernate 6 与数据访问层
Spring Boot 3.0 升级至 Hibernate 6.x。最显著的变化是 Hibernate Criteria API 的移除,长期被标记为废弃的 org.hibernate.Criteria 及其关联类已被删除。若项目仍在使用,需迁移至 JPA Criteria API 或 QueryDSL。
此外,Hibernate 6 对类型映射和 SQL 生成策略进行了调整,部分 MySQL/PostgreSQL 方言的默认行为发生变化,需在升级后运行全量集成测试验证 SQL 输出。
迁移实践策略
阶段一:Java 8 → Java 17(不升级 Spring Boot)
此阶段目标是在保持 Spring Boot 2.7.x 的前提下,将运行时升级至 Java 17。这能隔离 JVM 层面的问题,避免同时处理框架变更。
- 升级构建工具 JDK 版本:将
JAVA_HOME和 Maven/Gradle 的 toolchain 指向 JDK 17。 - 运行编译:捕获因移除 API 导致的编译错误(如
javax.xml.bind、sun.misc.Unsafe相关调用)。 - 添加替代依赖:对 JAXB、JAX-WS 等被移除模块,引入独立实现坐标。
- 处理强封装警告:Java 17 默认强烈封装 JDK 内部(
--illegal-access=deny)。若代码或第三方库通过反射访问sun.*、com.sun.*,需添加--add-opens参数或升级库版本。 - 运行测试套件:重点验证序列化/反序列化、反射操作、动态代理等边界场景。
阶段二:Spring Boot 2.7 → 3.x
在 Java 17 验证稳定后,启动框架升级。
- 升级 Spring Boot 版本号:修改
pom.xml或build.gradle,先升至 3.0.x 最新补丁版。 - 执行 Jakarta 命名空间迁移:使用 OpenRewrite 或 IntelliJ 一键替换。
- 升级第三方依赖:Spring Cloud、Micrometer、Feign 等需同步升级至兼容 Spring Boot 3 的版本。
- 审查安全配置:根据 Spring Security 6 的变更清单调整
SecurityFilterChain。 - 处理配置属性变更:启动时观察
spring-boot-properties-migrator输出的诊断信息,批量替换废弃属性。 - 处理 trailing slash 变化:检查 REST API 是否有客户端依赖带斜杠的 URL,必要时显式配置双路由或代理重写。
- 全量回归测试:包括单元测试、集成测试、契约测试和端到端测试。
阶段三:Java 17 → Java 21/25(可选)
若业务场景能从虚拟线程受益(高并发 I/O 密集型,如网关、代理、Web 服务),可进一步升级至 Java 21 或 25。
- 升级 JDK:切换运行时至 Java 21/25。
- 启用虚拟线程:Spring Boot 3.2+ 支持
spring.threads.virtual.enabled=true,Tomcat 和 WebFlux 自动使用虚拟线程处理请求。 - 审查同步代码:搜索
synchronized关键字,评估是否需替换为ReentrantLock以避免线程固定。 - 评估 ScopedValue:若 ThreadLocal 使用量大,试点替换为
ScopedValue。
常见问题与排障
--add-opens 与反射
Java 17 起,反射访问 JDK 内部包会触发 InaccessibleObjectException。典型场景:
- Gson、Jackson 等 JSON 库访问
java.base/java.lang下的私有字段。 - Spring 框架访问
ClassLoader内部结构。 - CGLIB 生成代理时访问
MethodHandles.Lookup。
不要在启动参数中无差别添加 --add-opens。正确做法是:
- 升级库版本至兼容 Java 17+ 的版本(如 Spring Framework 5.3.18+、Jackson 2.12+)。
- 若库已停止维护,评估替代方案。
- 仅在确认无升级路径时,针对性地添加
--add-opens。
sun.misc.Unsafe 的替代
Unsafe 在 Java 9 起被标记为内部 API,Java 17 起受强封装限制。若项目直接使用 Unsafe(常见于高性能计算或序列化框架),应迁移至标准 API:
Unsafe 操作 |
替代方案 |
|---|---|
allocateInstance |
MethodHandles.Lookup::defineClass 或 VarHandle |
putOrdered* / putVolatile* |
java.lang.invoke.VarHandle |
compareAndSwap* |
VarHandle::compareAndSet |
park / unpark |
LockSupport(已公开) |
Lombok 兼容性
Lombok 在旧版本中对 JDK 内部 API 的依赖会导致 Java 17 编译失败。需升级至 Lombok 1.18.22+,并在 lombok.config 中配置:
1 | |
迁移收益量化
从 Java 8 升级至 Java 21+ 的收益不仅体现在语言特性,更反映在运行时效率:
- G1 GC 改进:Java 9–21 期间 G1 的停顿时间预测和并发标记效率持续提升,同等堆大小下 Full GC 频率降低 30–50%。
- ZGC/Shenandoah:Java 11 引入的 ZGC 和 Shenandoah 提供亚毫秒级停顿,适合大堆(>32GB)低延迟场景。
- 虚拟线程吞吐:在高并发 I/O 场景下,虚拟线程可将同等硬件的吞吐提升数倍。Spring Boot 3.2+ 开启虚拟线程后,Tomcat 每个请求不再独占平台线程,同等并发量下内存占用显著下降。
- 启动时间:Java 9+ 的 AOT 编译(jaotc,实验性)和 CDS(Class Data Sharing)归档能显著缩短 JVM 启动时间。Spring Boot 3.x 配合 CDS 后,启动时间可缩短 20–40%。
工具链与自动化
OpenRewrite
OpenRewrite 是目前最成熟的自动化迁移框架。除 Spring Boot 升级 recipe 外,还提供 Java 版本升级 recipe:
org.openrewrite.java.migrate.Java8toJava11org.openrewrite.java.migrate.Java11toJava17org.openrewrite.java.migrate.UpgradeToJava21
这些 recipe 会自动处理 API 替换、弃用方法移除、try-with-resources 改进、集合工厂方法引入等任务。
jdeps
JDK 自带的 jdeps 工具可分析项目的模块依赖和内部 API 使用情况:
1 | |
输出会列出所有访问 JDK 内部 API 的类,以及建议的标准替代方案。
IntelliJ IDEA 检查
IntelliJ IDEA 2023.2+ 内置"Migrate to Java 21"检查,可检测:
- 可替换为
var的局部变量声明 - 可转换为 Records 的纯数据类
- 可应用模式匹配的
instanceof+ 强制转换组合 - 可替换为集合工厂方法的代码(如
List.of())
结论
从 Java 8 迁移到 Java 25 是一项涉及语言、运行时、框架和依赖链的系统性工程。核心风险不在语言语法本身,而在三方面:
- 强封装与反射:Java 9+ 的模块系统对反射和内部 API 的访问施加了硬性限制,第三方库若未跟进升级,会导致运行时异常。
- Jakarta EE 命名空间:Spring Boot 3.x 的
javax到jakarta迁移影响面大,需借助 OpenRewrite 等工具自动化处理。 - 安全配置与路径匹配:Spring Security 6 和 Spring Framework 6 的默认行为变化(trailing slash、dispatch type 鉴权)可能在升级后破坏现有 API 契约。
推荐采用渐进式策略:先升级 JDK 至 17 并稳定运行,再升级 Spring Boot 至 3.x,最后根据业务需求决定是否启用虚拟线程等 Java 21+ 特性。每个阶段保持完整的测试覆盖,避免多变量同时变更带来的排障困难。





