Gson 读取 Instant 报 InaccessibleObjectException,检查版本与 JSON 格式
JDK17反射访问Instant失败时核对Gson版本;2.14默认时间对象格式与ISO字符串适配器分别处理,保留接口兼容。
JDK 17 上反序列化带 Instant 字段的 DTO,报 java.base does not "opens java.time" 或无法访问 Instant.seconds,先检查实际加载的 Gson 版本。旧版 Gson 可能尝试反射读取 JDK 类型的私有字段;Gson 2.14.0 已增加 java.time 内置适配器,处理方式与旧版不同。
还要检查接口约定的 JSON。2.14.0 的 Instant 默认表示采用包含 seconds、nanos 的对象,并不会自动改成 ISO 8601 字符串。解决反射访问与保持现有 JSON 契约,需要一起考虑。
即使输入是空对象,也可能在建立适配器时失败
旧版会为 DTO 声明的字段类型创建适配器,未必等到 JSON 真的出现该字段才处理。例如 Gson 2.8.8 与 JDK 17 的组合,输入 {} 仍可能因声明的 Instant 字段触发私有反射访问。
因此,删除请求中的时间字段、改成 JSON null 或换一个空对象,不是可靠修复。查看堆栈中的类型和实际 Gson 依赖,包括框架带入的版本、运行包和自定义适配器注册位置。
JDK 17 强封装后,旧的 --illegal-access 不能恢复这种访问。异常提到自己的模块包,与这里提到的 JDK java.time 包是不同场景,不应把所有包都开放。
能升级时,先核对默认格式是否相容
Gson 2.14.0 的内置 Instant 适配器通过公开 API 读写,不需要反射其私有字段。若系统原本采用对象形态的 JSON,可以按依赖兼容要求升级,并用已有真实输入和输出检查表示是否一致。
若接口原本约定时间字符串,则仍要明确注册对应适配器。不能把升级理解成“所有时间格式现在都能读取”,也不能把 Instant 的修复推广到其他任意第三方类型。
字符串契约使用明确的 Instant 适配器
下面适用于约定 ISO 8601 字符串或 JSON null 的接口。它应合入真正执行序列化的 Gson 实例,保留已有适配器和其他设置:
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParseException;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.time.Instant;
import java.time.format.DateTimeParseException;
Gson gson = new GsonBuilder()
.registerTypeAdapter(Instant.class, new TypeAdapter<Instant>() {
@Override
public void write(JsonWriter out, Instant value) throws IOException {
out.value(value.toString());
}
@Override
public Instant read(JsonReader in) throws IOException {
if (in.peek() != JsonToken.STRING) {
throw new JsonParseException("Expected an Instant string");
}
try {
return Instant.parse(in.nextString());
} catch (DateTimeParseException e) {
throw new JsonParseException("Invalid Instant string", e);
}
}
}.nullSafe())
.create();
这是初始化片段,需放在实际 Java 方法或初始化位置中。nullSafe() 处理 JSON null 与 Java null,Instant.parse 处理带明确时刻的字符串。日期只有年月日、无时区本地时间或历史对象格式,不属于这个适配器接受的输入;不要给 LocalDate、LocalDateTime 直接复用它。
新建另一个 Gson 后未交给实际调用方,不会改变框架或工具类原有实例。已经注册适配器却仍见反射堆栈时,优先确认报错路径使用的实例和声明类型,而不是继续添加 JVM 开放参数。
验收格式与错误路径
保留旧依赖和输出契约,检查真实时间值、纳秒精度、null、缺省字段及非法输入,并对照下游实际读取格式。若采用自定义字符串适配器,不能让原本消费对象表示的下游无声切换。
--add-opens 会扩大指定包的反射访问范围;它可以改变旧版行为,却不会定义稳定的 JSON 格式。优先采用合适的正式版本或明确适配器,移除不再需要的临时开放配置后再次验证。
升级后若异常指向其他类型,按新的字段与包重新检查。不要以一次 Instant 调用成功,认定所有模块反射问题都已消失。


