Java 8 升级 Java 21 实战:用 jdeprscan 找出 JAXB、反射和默认字符集风险
来源:17golang原创
时间:2026-07-26 10:13:08 425浏览 收藏
一套跑了很多年的Java 8订单服务,换成JDK 21之后,最先冒出来的往往不是业务代码直接报错,而是构建阶段找不到JAXB类、启动时反射访问直接被拦,还有同一份文本在不同机器上解析出来的结果对不上。正式升级之前先把这三类风险分开排查清楚,通常比直接替换Docker基础镜像要省不少时间。
要点速览
- 先用
jdeprscan --release 21扫描依赖和编译产物,再判断项目是不是真的能正常跑起来。 - JDK 11 已经不再内置捆绑 JAXB、JAX-WS,XML 相关代码要改成显式声明依赖,不能再依赖 JDK 自带的模块。
- JDK 17 之后强封装机制默认生效,
--add-opens只能作为短期过渡方案,不能当成永久解法。 - 涉及文本处理、CSV 解析、配置文件读取的逻辑,必须显式传入
StandardCharsets.UTF_8,别把机器自带的默认字符集当成默认协议。
先把 Java 8 到 Java 21 的升级范围划定清楚
别一上来就把所有第三方依赖全升到最新版本。更稳妥的做法是先保留原有业务代码的所有依赖版本,只替换构建镜像和运行时环境,然后记录第一批出现的失败点。这样就能明确区分出问题是「JDK 版本变化」导致的,还是「框架本身升级」带来的故障。
迁移检查可以按下面的优先级顺序推进:
| 检查对象 | 典型报错信号 | 处理方向 |
|---|---|---|
| JDK API | 类或模块不存在 | 补充独立依赖或者替换成对应新API |
| 内部反射逻辑 | InaccessibleObjectException | 优先升级对应依赖库,最后才考虑开放包权限 |
| 文本输入输出 | 中文变成问号或者字段直接错位 | 显式指定 UTF-8 字符集并补充回归测试样本 |
这张表的核心价值是先做分层排查。比如 JAXB 报错属于依赖装配问题,和 Spring 容器能不能正常启动没有直接关联;一上来就去改业务Bean的代码,通常只会把排查范围越改越大,反而拖慢进度。

用 jdeprscan 找到会在新 JDK 里暴露的旧 API
先构建一份没有经过混淆、和生产环境完全一致的JAR包,再执行扫描操作。命令里的 --release 21 表示按照目标JDK的弃用和移除清单做校验,不是把代码直接编译成Java 21版本。
mvn -DskipTests package jdeprscan --release 21 target/order-service.jar
如果扫描结果里出现了某个第三方库的类名,先用依赖树定位到它是哪个依赖的哪个版本引入的:
mvn dependency:tree -Dincludes=javax.xml.bind jdeps --multi-release 21 --summary target/order-service.jar
这里不用急着把所有警告全都压掉。迁移记录至少要记清楚三列:调用方、替代方案、验证用例。只在日志里看到一条提示,并不等于应用已经完全失效;但那些已经被标记为移除风险的API,建议在本次升级过程中就完成替换或者隔离处理。
JAXB 缺失时,改依赖边界而不是直接加启动参数
JDK 8 时代,下面这类导入语句经常会出现「看起来不用额外配置就能直接编译」的情况:
import javax.xml.bind.JAXBContext; import javax.xml.bind.Marshaller;
从 JDK 11 开始 JAXB 就不再属于 JDK 自带的模块了。应用如果仍然需要用到XML绑定能力,应该根据项目实际使用的JAXB实现,显式声明API和运行时依赖,并且确认打包后的运行环境也能正常加载到这些类。对于只用到了Base64编解码的旧代码,优先换成Java 8本身就已经提供的 java.util.Base64,不要为了保留一个工具类就把整套旧模块都给引回来。
验证的时候不要只看编译能通过就完事,至少跑一次「订单XML入参 → Java对象 → XML出参」的往返测试,检查命名空间、空字段和中文内容的解析结果是否符合预期。命令行能正常启动但是XML样本解析失败,说明依赖包已经打进包里了,但是绑定配置还没对齐。
遇到强封装反射报错,先查库版本再决定要不要开放包权限
从 JDK 17 开始,应用默认不能随便反射访问JDK的内部实现类。碰到这类问题的典型信号是:
java.lang.reflect.InaccessibleObjectException: Unable to make ... accessible: module java.base does not "opens ..."
排查路径建议固定成三步:第一步看完整的异常堆栈,确认发起反射操作的是自己写的业务代码,还是序列化、代理、字节码生成这类第三方库;第二步检查对应库有没有支持JDK 17/21的更新版本;第三步才在明确风险的前提下使用 --add-opens 作为临时过渡方案。
比如临时兼容的启动参数可以这么写:
java --add-opens java.base/java.lang=ALL-UNNAMED -jar order-service.jar
但这类参数应该记录到迁移台账里,同时设置好后续的删除期限。它只是临时扩大了模块边界,并不等于对应的库已经完全兼容新JDK;升级库或者改成公开API调用之后,最好把这些参数删掉,再跑一轮启动测试和集成测试确认没问题。

把默认字符集从隐含的环境依赖改成显式约定
迁移过程中最隐蔽的一类问题,就是开发机、容器环境和线上机器的默认字符集不一样。文件读取、CSV导入、HTTP签名或者消息摘要逻辑,只要有一处直接使用了平台默认值,就很容易出现「本地测试全正常、线上校验直接失败」的诡异情况。
// 不把运行环境当成协议
try (var reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
return reader.lines().toList();
}
String body = new String(bytes, StandardCharsets.UTF_8);
同时检查 new String(bytes)、FileReader、FileWriter 以及第三方CSV处理客户端的默认配置。回归测试的样本不要只放ASCII字符,至少要加入中文、emoji和不同系统的换行符,做字节级的签名结果对比。
用一轮可回滚的验证流程收尾
把升级流程拆成编译、启动、接口、数据和回滚五个校验门槛。任意一个门槛校验不通过,都保留原来的JDK 8镜像和原依赖锁定文件,不要在同一次发布过程里继续叠加框架的大版本升级。
- 编译:依赖树中不再依赖JDK内置的JAXB模块。
- 启动:日志里没有未处理的反射开放异常。
- 接口:XML、JSON、签名和文件下载的全量样本校验通过。
- 数据:中文、空值、时区和换行符的回归结果前后一致。
- 回滚:切回JDK 8镜像之后,旧发布包仍然能被原有部署脚本识别正常运行。
相关问题
Java 8 项目一定要一次升级到 Java 21 吗?
不一定。如果依赖链非常老旧,可以先在隔离分支里完成JDK 11的移除项清理,再逐步切到JDK 17或者21;核心原则是每次升级只引入一组可以完整验证的变化。
加上 --add-opens 之后能不能长期使用?
不建议。它只适合紧急救急和给依赖库升级争取缓冲时间,长期保留会隐藏内部API依赖,也会扩大运行时的权限边界。
怎么判断默认字符集问题已经完全修好?
在不同操作系统和容器环境下,用同一组中文、emoji、换行符样本对比文件字节、接口摘要和解析后的字段结果,而不是只看页面显示出来的内容看起来正常就完事。
迁移清单可以先从三件小事开始
先固定目标JDK和构建镜像,再扫描编译产物处理JAXB依赖,最后把所有文本处理入口改成显式指定字符集。反射相关的问题则按「堆栈定位—确认库版本—临时开放权限—最终删除参数」的顺序逐步收敛。这样升级完成之后留下的是一份可以复查的变更记录,而不是一串只在某台特定机器上才能生效的启动参数。
-
479 收藏
-
337 收藏
-
128 收藏
-
149 收藏
-
202 收藏
-
200 收藏
-
352 收藏
-
文章 · java教程 | 5小时前 | 消息队列 · Java · 事务 · 架构设计 · Spring Boot · spring boot 最终一致性 事务消息表 Outbox Pattern 订单通知498 收藏
-
文章 · java教程 | 2天前 | 文件处理 · 配置管理 · Java · 命令行工具 · nio · Java Files.mismatch 配置目录校验 Files.mismatch Java文件对比371 收藏
-
284 收藏
-
文章 · java教程 | 3天前 | Java · HTTP · ndjson · httpclient · 性能实践 · 流式读取 背压 Java HttpClient NDJSON BodyHandlers.ofLines309 收藏
-
文章 · java教程 | 5天前 | 并发 · Java · CompletableFuture · Java CompletableFuture 任务取消 orTimeout completeOnTimeout152 收藏
-
300 收藏
-
文章 · java教程 | 1星期前 | 事务 · spring · aop · Java教程 · Transactional · 排错 · java Spring 事务失效 @Transactional AOP代理 同类方法调用 订单创建406 收藏
-
文章 · java教程 | 1星期前 | 数据库 · 性能优化 · Java教程 · Optional · orElseGet · java 数据库查询 懒加载 optional supplier orElse orElseGet238 收藏
-
文章 · java教程 | 1星期前 | 数据库 · 性能优化 · Java教程 · Optional · orElseGet · java 数据库查询 懒加载 optional supplier orElse orElseGet186 收藏
-
文章 · java教程 | 1星期前 | Record · Java教程 · 防御式拷贝 · List.copyOf · Arrays.copyOf · 不可变性 · arrays.copyof 可变集合 Java record List.copyOf 防御式拷贝 数组克隆247 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习