InaccessibleObjectException在JDK17+爆发是因JPMS强制执行强封装,java.base等核心模块默认不opens包给未命名模块;--add-opens需精准配置如--add-opens java.base/java.lang=ALL-UNNAMED,且必须作为JVM启动参数置于正确位置,否则无效。

直接加 --add-opens 参数就能跑起来,但加错位置、加错包名、或加得过多,反而会掩盖真实问题,甚至在 JDK 21+ 上失效。
为什么 InaccessibleObjectException 在 OpenJDK 17+ 突然爆发
这不是 OpenJDK 特有 bug,而是模块系统(JPMS)的强制执行结果。JDK 9 引入模块后,java.base 这类核心模块默认不 opens 任何包给未命名模块(即你 classpath 下的传统代码)。框架如 Spring、ShardingSphere、cglib、Jackson 都依赖反射访问 java.lang、java.util 甚至 java.io 里的私有字段或构造器——这些操作在 JDK 8 是透明的,在 JDK 17+ 就被拦住并抛出异常。
典型错误信息里那句 module java.base does not "opens java.lang" to unnamed module 就是模块系统的“拒绝声明”,不是代码写错了,是权限没开。
--add-opens 必须加对目标包和模块
参数格式是 --add-opens <source-module>/<package>=<target-module></target-module></package></source-module>,最常见写法是:
-
--add-opens java.base/java.lang=ALL-UNNAMED:解决ClassLoader.defineClass、String(char[], boolean)等反射失败 -
--add-opens java.base/java.util=ALL-UNNAMED:应对HashMap.table、TimerTask.state等字段访问报错 -
--add-opens java.base/java.io=ALL-UNNAMED:修复部分序列化库(如旧版 Kryo)对ObjectStreamClass的反射调用
注意:ALL-UNNAMED 指的是你项目 jar 和所有依赖所在的“未命名模块”,不是通配符;不能写成 * 或 ALL。漏掉 java.util 却只开 java.lang,照样会在运行时某处崩。
不同启动方式下,--add-opens 必须配在正确位置
这个参数是 JVM 启动参数,不是应用参数,加错地方等于没加:
- Maven 命令行启动:
mvn spring-boot:run -Dspring-boot.run.jvmArguments="--add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED" - IDE(IntelliJ/Eclipse):在 Run Configuration → VM options 里填写,不要塞进 Program arguments
- 打包后运行:
java --add-opens java.base/java.lang=ALL-UNNAMED -jar app.jar,必须放在-jar前面 - Spring Boot Maven 插件配置(pom.xml)需显式配置
<jvmarguments></jvmarguments>,否则mvn install阶段不会生效
常见误区:把参数写在 application.properties 里,或当成系统属性传(-D),完全无效。
JDK 21+ 开始,--add-opens 不再是长期解法
JDK 21 起,--add-opens 仍可用,但 Oracle 明确建议逐步淘汰。真正稳定的路径是:
- 升级框架版本:Spring Boot 3.2+、Hibernate 6.4+、Jackson 2.15+ 已主动适配模块化,减少对内部 API 的依赖
- 替换底层库:用 ByteBuddy 替代 cglib,用 JBoss Modules 或自定义模块描述符替代裸反射
- 检查是否真需要反射:比如
Object.clone()调用,可改用拷贝构造器或 record + with;HashMap.table访问,应改用size()或entrySet()等公开 API
现在加 --add-opens 是为了快速验证和过渡,但它不能解决模块边界模糊带来的维护成本——下次升级到 JDK 23,可能又得加新包。最易被忽略的一点是:日志里报错的包名,往往只是第一层触发点,背后可能还藏着 java.time、java.net 等其他包的隐式反射调用,得靠 --illegal-access=debug(JDK 16–17)或 --add-opens 逐个试出来。


















