从 Java 8 直接跳到 Java 25,一次就够了
摘要:Java 8 官方支持已结束,社区也不再维护。继续留在 Java 8 等于裸奔——没安全补丁、没性能优化、用不了虚拟线程和 Record。本文不翻译官方文档,只讲一个 Java 8 老兵的真实升级经历:从 8 直奔 25,踩了什么坑、怎么解的、值不值。顺便告诉你为什么别在 17 或 21 浪费时间——跳一次就够,管到 2033。
背景:Java 8 真的不能再用了
上个月,组里一个新同事问我:「这个项目为什么还在用 Java 8?」
我愣了一下。说实话,不是不想升,是一想到升级的麻烦,就一年拖一年。项目跑了快十年,稳得像块石头。谁愿意动它?
直到有一天,安全团队扫出一堆 CVE——全是 Java 8 的洞,没人修了。
Java 8 是 2014 年发布的。Oracle 的官方支持早就停了,社区支持也在今年彻底终止。换句话说,你的 Java 8 应用,现在连安全补丁都拿不到了。
而另一边,同事们的新项目用上了虚拟线程、Record、模式匹配 switch——三十行代码干完我三百行的事。每次 Code Review,我都在关掉那个手写了两百行 getter 的类。
不升不行了。
为什么直接跳到 25,不是 17 也不是 21
决定升级之前,我先把几个 LTS 版本捋了一遍:
| 版本 | 发布时间 | 支持截止 | 关键卖点 |
|---|---|---|---|
| Java 8 | 2014.03 | 已终止 | 你正在用的 |
| Java 17 | 2021.09 | 约 2029 | 封印类、Record 成熟 |
| Java 21 | 2023.09 | 约 2031 | 虚拟线程正式版 |
| Java 25 | 2025.09 | 至少 2033 | 紧凑对象头、18 项 JEP、最新 LTS |
算一笔账就清楚了。
方案 A:8 → 17 → 21 → 25。踩三次坑,折腾三轮。每次升级都得处理模块化、废弃 API、第三方依赖兼容性。三次下来,光排查问题就能掉半条命。
方案 B:8 → 25,只跳一次。坑集中踩完,一步到位。Java 25 是 2025 年 9 月发布的 LTS,Oracle 承诺至少支持 8 年——从现在算起能管到 2033 年。下一个 LTS 什么时候出还不一定。从生命周期看,25 是眼下最「长命」的选择。
至于 Java 26(2026 年 3 月发布,非 LTS)和还没定版的 Java 27——非 LTS 版本只支持 6 个月,根本不值得考虑。
结论:别走 17 过渡。坑,一次踩完就够了。
升级实战:从 8 到 25 的四道坎
跨了 9 个大版本,坑是不可避免的。下面这四个是我实打实遇到的。
第一坎:模块化系统(Java 9)
Java 9 引入了 JPMS(Java Platform Module System),最直接的影响就是 sun.* 内部 API 被封了。
编译直接报错:
java.lang.reflect.InaccessibleObjectException:
Unable to make field private final byte[] java.lang.String.value
accessible: module java.base does not "opens java.lang" to unnamed module
如果你的项目或依赖里用了反射访问 JDK 内部类,升级后必炸。
解法分两步。先用 JDK 自带的 jdeps 扫一遍:
jdeps --jdk-internals your-app.jar
它会列出所有对 sun.* 等内部 API 的调用。能改的就改掉——大部分场景 JDK 自己已经提供了替代 API。
实在改不了的(比如某些老框架深度依赖),用 --add-opens 临时放行:
java --add-opens java.base/java.lang=ALL-UNNAMED \
--add-opens java.base/java.util=ALL-UNNAMED \
-jar your-app.jar
别把这当永久方案。--add-opens 是「先让系统跑起来」的止血措施,后面要逐个清理。
第二坎:被移除的 API(Java 11/17)
从 Java 8 到 25,JDK 删了不少东西。最坑的两个:
JAXB / JAX-WS 被移除。如果你的项目用了 javax.xml.bind.* 做 XML 处理,升级后直接 ClassNotFoundException。
解法:Maven 加独立依赖:
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>4.0.2</version>
</dependency>
Nashorn JS 引擎被干掉。如果代码里调了 ScriptEngineManager 跑 JavaScript,得换 GraalJS。
这两个问题不复杂,但排查起来很耗时——它们不会在编译期报错,只会在运行时崩。
第三坎:老库不兼容
这是我们升级中花时间最多的一块。
最大的雷是 fastjson 1.x 不支持 Record 序列化。Java 16 引入的 Record 类,fastjson 1.x 根本不认识,序列化出来是空对象。解法就一条:升到 fastjson2。
其他几个常见依赖升级:
- Lombok:升到 1.18.36+,否则 Java 25 的 class file 版本不认
- Byte Buddy / CGLIB:各种 AOP 框架底层依赖,务必升到最新
- Hibernate:至少 6.x,5.x 对 Java 17+ 的兼容性已经很差了
Maven 的 maven-enforcer-plugin 可以帮你检查依赖的 JDK 兼容性,强烈建议先跑一遍。
第四坎:TLS 和 SecurityManager
两个安全相关的变化,很容易被忽略。
TLS 1.0/1.1 默认禁用(Java 17 开始)。如果你的应用连的是老版本 MySQL、Redis 或者其他老服务,SSL 握手直接失败。日志里会看到 SSLHandshakeException。
短期解法:修改 java.security 配置文件,把 jdk.tls.disabledAlgorithms 里的 TLSv1 和 TLSv1.1 去掉。长期方案:升级数据库和服务端。
SecurityManager 永久禁用(Java 25)。之前几个版本只是标记 deprecated,Java 25 直接不给用了。启动时如果带了 -Djava.security.manager 参数,进程起不来。如果你的应用没用到 SecurityManager——大多数 Java 8 项目都不会用——这事跟你没关系。但如果之前配过,必须去掉。
踩坑速查表
做升级那两周,我建了个备忘录。后来发现同事也遇到一样的问题,干脆整理成表:
| 报错信息 | 原因 | 解法 |
|---|---|---|
InaccessibleObjectException |
反射访问内部 API | jdeps --jdk-internals 扫描,--add-opens 放行 |
ClassNotFoundException: javax.xml.bind.* |
JAXB 被移除 | 加 jakarta.xml.bind-api 依赖 |
ClassNotFoundException: jdk.nashorn.* |
Nashorn 被移除 | 换 GraalJS |
SSLHandshakeException |
TLS 1.0/1.1 被禁用 | 升级数据库/服务端,或临时放行 |
UnsupportedClassVersionError |
class file 版本不匹配 | 升 Lombok / Byte Buddy / ASM |
| Record 序列化为空 JSON | fastjson 1.x 不支持 | 升到 fastjson2 |
| SecurityManager 启动报错 | Java 25 直接禁了 | 去掉 -Djava.security.manager |
迁移工具——OpenRewrite 一把梭
踩完前面那些坑之后,我才发现其实有更好的方式。
OpenRewrite 是一个 Maven 插件,能自动帮你改代码——替换废弃 API、升级依赖版本、迁移到新语法。你跑一下,它能处理掉大约 60-70% 的机械化改动。
配置很简单,在 pom.xml 加一段:
<plugin>
<groupId>org.openrewrite.maven</groupId>
<artifactId>rewrite-maven-plugin</artifactId>
<version>5.43.0</version>
<configuration>
<activeRecipes>
<recipe>org.openrewrite.java.migrate.jakarta.JavaxMigrationToJakarta</recipe>
<recipe>org.openrewrite.java.migrate.UpgradeToJava25</recipe>
</activeRecipes>
</configuration>
</plugin>
然后跑:
mvn rewrite:run
它会自动扫项目、改代码、生成 diff。改完之后 git diff 看一遍,确认没问题再提交。
三件 OpenRewrite 帮不了你的事:
- 业务逻辑层面的调整(比如你针对旧 API 写的 workaround)
- 运行时配置(
--add-opens、java.security这些得手配) - 第三方依赖的内部兼容性问题
推荐组合拳:OpenRewrite 扫一遍 → jdeps + jdeprscan 各跑一次 → maven-enforcer-plugin 检查依赖 → 剩下的手动改。
升级后开哪些新特性(按性价比排序)
费这么大劲升上来,不开新特性就亏了。按收益从高到低排:
1. 紧凑对象头(零代码改动)
性价比最高,没有之一。加一个 JVM 参数就生效:
-XX:+UseCompactObjectHeaders
对象头从 12 字节压缩到 8 字节。实测下来,堆内存降 3-5%,CPU 开销最高降 30%。什么都不用改,重启就能吃的性能红利。
2. 虚拟线程(低改动,高收益)
原来用线程池处理并发任务,一百个线程就是一百个平台线程,内存和上下文切换开销都不小。虚拟线程一个平台线程上能挂成千上万个:
var executor = Executors.newVirtualThreadPerTaskExecutor();
executor.submit(() -> {
// 处理请求,该阻塞就阻塞,不怕
});
百万级并发不是梦。Java 24 还修了 synchronized 导致的 pinning 问题,到 25 已经相当稳了。
3. Record + 模式匹配(写法爽)
告别手写 getter/setter/toString:
record User(Long id, String name, String email) {}
配合模式匹配 switch:
var result = switch (obj) {
case User(var id, var name, _) -> "用户: " + name;
case Order(var orderId, var amount) -> "订单: " + orderId;
default -> "未知类型";
};
代码量砍一半,可读性翻倍。写惯了之后,回到 Java 8 会觉得自己在写废代码。
4. 其他锦上添花
- 文本块(Java 15):三引号写 SQL/JSON,不用再拼
+ "\n"了 - 向量 API(Java 16-25 正式版):做 AI 和数据处理,SIMD 指令直接上,不用 JNI
- 密封类(Java 17):限制继承关系,编译器帮你把关
总结
升级这事,越想越怕。真动手了,也就两周。
从 Java 8 直奔 25,踩一次坑,管到 2033。 别走 17 过渡——每多一次过渡就多一轮排查,纯粹浪费时间。
如果你们组还在 Java 8 上,我的建议是:先拿一个非核心服务试水,把上面这些坑趟一遍。跑稳了,再把核心服务搬过来。等你用上虚拟线程和 Record,再回头写 Java 8 代码,会觉得像在用十年前的手机。
参考:
- Oracle Java SE Support Roadmap: oracle.com
- Java 25 LTS 升级指南: xlabs.club
- JDK Release Matrix: javaalmanac.io
作者:唐悦玮 | 公众号同名
从后端出发,用 AI 拓展到全栈的工程师。
更多推荐
所有评论(0)