jdk 9+ 升级后反射报错需精准配置 --add-opens 参数:先从 inaccessibleobjectexception 堆栈提取类名(如 java.io.file),查其所属模块(java.base)和包名(java.io),拼出 --add-opens java.base/java.io=all-unnamed,并确保置于 jvm 启动参数正确位置(如 idea 的 vm options、maven 的 -jar 前等),多包需重复书写,禁用非法通配语法。

直接加 --add-opens 参数不是万能解法,但不加它,JDK 9+ 升级后反射必崩——关键在于加在哪、加多少、加得准不准。
报错堆栈里第一个类名就是突破口
看到 InaccessibleObjectException: Unable to make field private java.lang.String java.io.File.path accessible,别急着搜解决方案。先抓出 java.io.File 这个全限定类名:
- 查它归属模块:
jdeps -s java.io.File或翻 Oracle 官方模块文档 → 确认属于java.base - 取包名:去掉类名最后一段 →
java.io(不是java.io.*,也不是java) - 目标模块写死为
ALL-UNNAMED(你的 Spring Boot 应用和所有依赖 JAR 都跑在这) - 拼出参数:
--add-opens java.base/java.io=ALL-UNNAMED
JVM 启动参数必须放对位置才生效
--add-opens 是 JVM 启动参数,不是编译参数,也不是运行时动态配置项。塞错地方等于没加:
- IDEA 运行配置:在「Run → Edit Configurations → VM options」里粘贴,**不能**填在「Program arguments」
- Maven 打 fat jar 后执行:
java --add-opens java.base/java.lang=ALL-UNNAMED -jar app.jar,参数必须在-jar前 - Spring Boot Maven 插件启动:
spring-boot-maven-plugin配<jvmarguments></jvmarguments>,不是<arguments></arguments> - Gradle 测试:必须在
test { jvmArgs(...) }块里写,build.gradle顶层的java { jvmArgs }不生效
哪些地方必须开,哪些其实可以绕过
不是所有反射访问都要硬开模块口子。优先级很明确:
- 必须配:
java.base/java.lang(Class、ClassLoader相关)、java.base/java.util(ArrayList.elementData)、java.base/java.time(Spring Boot 2.7.x 绑定 LocalDateTime 报错) - 按需配:若用
@ConfigurationProperties绑定 URL、InetAddress 等类型,可能还需java.base/java.net - 可绕过:单元测试里自己写的私有字段反射,改用同包 + package-private +
@TestInstance生命周期控制,比开模块更干净
别信“通配”或“全开”,语法错误会静默失效
--add-opens java.base/ALL-UNNAMED=ALL-UNNAMED 是非法语法,JVM 直接忽略;--add-opens java.base/=ALL-UNNAMED 也无效。必须严格遵循「源模块/包 = 目标模块」格式:
- 反射
sun.misc.Unsafe→--add-opens java.base/jdk.internal.misc=ALL-UNNAMED - 反射
java.lang.invoke.MethodHandles.Lookup→--add-opens java.base/java.lang.invoke=ALL-UNNAMED - 多个包要开?重复写多条,空格分隔:
--add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.nio=ALL-UNNAMED
最容易被忽略的是:报错堆栈里出现的类,未必是你代码写的,很可能是 Spring、Jackson、CGLIB 内部硬编码的反射调用——你得顺着它报的类名一路反推,而不是猜“大概要开 java.base 就行”。











