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 文档以及生产环境的实际迁移案例。

Java 8 到 Java 25 迁移路线图


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。推荐的两阶段策略:

  1. 先迁移到 Java 17 + Spring Boot 2.7.x:验证兼容性,解决模块系统和依赖冲突。
  2. 再升级到 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
2
// Java 8 可编译,Java 11 报错:package javax.xml.bind does not exist
import javax.xml.bind.JAXBContext;

解决路径:在 Maven/Gradle 中显式引入独立实现的依赖,如 jakarta.xml.bind:jakarta.xml.bind-apiorg.glassfish.jaxb:jaxb-runtime

Java 10 引入的 局部变量类型推断(var 在 Java 11 中定型。它仅适用于局部变量,不改变运行时类型系统:

1
2
3
4
5
// 合法
var list = new ArrayList<String>();

// 不合法:无法推断右侧类型
var value = null;

Java 12–17:语法现代化

Java 14 引入的 Records 提供不可变数据的紧凑声明:

1
2
3
4
public record User(String name, Integer age) {}

// 自动生成 constructor、getter、equals、hashCode、toString
User user = new User("Alice", 30);

Records 的设计意图是承载纯数据,而非行为。其字段隐式为 final,不可通过反射修改内部状态。

Java 17 定型的 密封类(Sealed Classes) 允许限制继承体系:

1
2
public abstract sealed class Shape
permits Circle, Rectangle, Square {}

这对框架设计和领域建模有直接影响——Spring Data 等库从 3.x 开始利用密封类优化存储库代理生成。

模式匹配(Pattern Matching) 历经多轮预览,在 Java 21 中完全定版。switch 表达式可匹配类型、解构记录并绑定变量:

1
2
3
4
5
6
7
8
static String describe(Object obj) {
return switch (obj) {
case User u when u.age() >= 18 -> "Adult: " + u.name();
case User u -> "Minor: " + u.name();
case null -> "null";
default -> "Unknown";
};
}

Java 21–25:并发模型与运行时革新

Java 21 的核心交付是 虚拟线程(Virtual Threads),源自 Project Loom。虚拟线程由 JVM 调度而非操作系统内核,单个线程栈仅占用数百字节到数千字节,使得百万级并发连接在常规堆内存配置下可行:

1
2
3
4
5
6
7
8
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 1_000_000).forEach(i ->
executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1));
return i;
})
);
} // executor.close() 自动等待所有任务完成

虚拟线程与平台线程共享 java.lang.Thread API,现有同步代码无需改写即可运行。但有两类场景需要审查:

  1. 使用 synchronized 的代码块:若虚拟线程在 synchronized 内执行阻塞操作(如 I/O),该虚拟线程会被固定(pinned)到平台线程,导致无法被调度器卸载。应优先使用 java.util.concurrent.locks.ReentrantLock
  2. ThreadLocal 的滥用:虚拟线程数量庞大时,ThreadLocal 的内存占用会被放大。Java 21 引入的 ScopedValue(孵化中)提供更有范围的值绑定机制。

Java 21 同时引入 Sequenced Collections 接口(SequencedCollectionSequencedSetSequencedMap),为有序集合统一了 addFirstaddLastreversed 等操作。

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
2
3
4
5
6
7
8
9
10
<plugin>
<groupId>org.openrewrite.maven</groupId>
<artifactId>rewrite-maven-plugin</artifactId>
<version>5.x</version>
<configuration>
<activeRecipes>
<recipe>org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0</recipe>
</activeRecipes>
</configuration>
</plugin>
  • 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 或旧配置方式时打印警告。推荐路径:

  1. 在 Spring Boot 2.7 下升级至 Spring Security 5.8。
  2. 根据警告信息调整安全配置。
  3. 升级至 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
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-properties-migrator</artifactId>
<scope>runtime</scope>
</dependency>

完成迁移后应移除此依赖。

其他值得注意的核心变更:

  • Trailing slash 匹配默认关闭:Spring Framework 6 将 PathPatternParser 设为默认,此前 /path/path/ 等效,现在后者返回 404。如需恢复旧行为,显式配置 setUseTrailingSlashMatch(true)
  • Image banner 移除banner.gifbanner.jpgbanner.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 层面的问题,避免同时处理框架变更。

  1. 升级构建工具 JDK 版本:将 JAVA_HOME 和 Maven/Gradle 的 toolchain 指向 JDK 17。
  2. 运行编译:捕获因移除 API 导致的编译错误(如 javax.xml.bindsun.misc.Unsafe 相关调用)。
  3. 添加替代依赖:对 JAXB、JAX-WS 等被移除模块,引入独立实现坐标。
  4. 处理强封装警告:Java 17 默认强烈封装 JDK 内部(--illegal-access=deny)。若代码或第三方库通过反射访问 sun.*com.sun.*,需添加 --add-opens 参数或升级库版本。
  5. 运行测试套件:重点验证序列化/反序列化、反射操作、动态代理等边界场景。

阶段二:Spring Boot 2.7 → 3.x

在 Java 17 验证稳定后,启动框架升级。

  1. 升级 Spring Boot 版本号:修改 pom.xmlbuild.gradle,先升至 3.0.x 最新补丁版。
  2. 执行 Jakarta 命名空间迁移:使用 OpenRewrite 或 IntelliJ 一键替换。
  3. 升级第三方依赖:Spring Cloud、Micrometer、Feign 等需同步升级至兼容 Spring Boot 3 的版本。
  4. 审查安全配置:根据 Spring Security 6 的变更清单调整 SecurityFilterChain
  5. 处理配置属性变更:启动时观察 spring-boot-properties-migrator 输出的诊断信息,批量替换废弃属性。
  6. 处理 trailing slash 变化:检查 REST API 是否有客户端依赖带斜杠的 URL,必要时显式配置双路由或代理重写。
  7. 全量回归测试:包括单元测试、集成测试、契约测试和端到端测试。

阶段三:Java 17 → Java 21/25(可选)

若业务场景能从虚拟线程受益(高并发 I/O 密集型,如网关、代理、Web 服务),可进一步升级至 Java 21 或 25。

  1. 升级 JDK:切换运行时至 Java 21/25。
  2. 启用虚拟线程:Spring Boot 3.2+ 支持 spring.threads.virtual.enabled=true,Tomcat 和 WebFlux 自动使用虚拟线程处理请求。
  3. 审查同步代码:搜索 synchronized 关键字,评估是否需替换为 ReentrantLock 以避免线程固定。
  4. 评估 ScopedValue:若 ThreadLocal 使用量大,试点替换为 ScopedValue

常见问题与排障

--add-opens 与反射

Java 17 起,反射访问 JDK 内部包会触发 InaccessibleObjectException。典型场景:

  • Gson、Jackson 等 JSON 库访问 java.base/java.lang 下的私有字段。
  • Spring 框架访问 ClassLoader 内部结构。
  • CGLIB 生成代理时访问 MethodHandles.Lookup

不要在启动参数中无差别添加 --add-opens。正确做法是:

  1. 升级库版本至兼容 Java 17+ 的版本(如 Spring Framework 5.3.18+、Jackson 2.12+)。
  2. 若库已停止维护,评估替代方案。
  3. 仅在确认无升级路径时,针对性地添加 --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
lombok.addLombokGeneratedAnnotation = true

迁移收益量化

从 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.Java8toJava11
  • org.openrewrite.java.migrate.Java11toJava17
  • org.openrewrite.java.migrate.UpgradeToJava21

这些 recipe 会自动处理 API 替换、弃用方法移除、try-with-resources 改进、集合工厂方法引入等任务。

jdeps

JDK 自带的 jdeps 工具可分析项目的模块依赖和内部 API 使用情况:

1
jdeps --jdk-internals -recursive target/classes

输出会列出所有访问 JDK 内部 API 的类,以及建议的标准替代方案。

IntelliJ IDEA 检查

IntelliJ IDEA 2023.2+ 内置"Migrate to Java 21"检查,可检测:

  • 可替换为 var 的局部变量声明
  • 可转换为 Records 的纯数据类
  • 可应用模式匹配的 instanceof + 强制转换组合
  • 可替换为集合工厂方法的代码(如 List.of()

结论

从 Java 8 迁移到 Java 25 是一项涉及语言、运行时、框架和依赖链的系统性工程。核心风险不在语言语法本身,而在三方面:

  1. 强封装与反射:Java 9+ 的模块系统对反射和内部 API 的访问施加了硬性限制,第三方库若未跟进升级,会导致运行时异常。
  2. Jakarta EE 命名空间:Spring Boot 3.x 的 javaxjakarta 迁移影响面大,需借助 OpenRewrite 等工具自动化处理。
  3. 安全配置与路径匹配:Spring Security 6 和 Spring Framework 6 的默认行为变化(trailing slash、dispatch type 鉴权)可能在升级后破坏现有 API 契约。

推荐采用渐进式策略:先升级 JDK 至 17 并稳定运行,再升级 Spring Boot 至 3.x,最后根据业务需求决定是否启用虚拟线程等 Java 21+ 特性。每个阶段保持完整的测试覆盖,避免多变量同时变更带来的排障困难。


参考资料