google/gson · error · RuntimeException

Unexpected ReflectiveOperationException occurred (Gson " + G

Error message

Unexpected ReflectiveOperationException occurred (Gson " + GsonBuildConfig.VERSION + "). To support Java records, reflection is utilized to read out information about records. All these invocations happens after it is established that records exist in the JVM. This exception is unexpected behavior.

What it means

Gson detects record support on the JVM and uses reflection (Class.isRecord, RecordComponent, canonical constructor) to handle Java records. If any of these reflective calls throws ReflectiveOperationException after the initial capability check, Gson treats it as truly unexpected behavior and throws a RuntimeException asking to be reported.

Source

Thrown at gson/src/main/java/com/google/gson/internal/reflect/ReflectionHelper.java:211

  }

  public static <T> Constructor<T> getCanonicalRecordConstructor(Class<T> raw) {
    return RECORD_HELPER.getCanonicalRecordConstructor(raw);
  }

  public static RuntimeException createExceptionForUnexpectedIllegalAccess(
      IllegalAccessException exception) {
    throw new RuntimeException(
        "Unexpected IllegalAccessException occurred (Gson "
            + GsonBuildConfig.VERSION
            + "). Certain ReflectionAccessFilter features require Java >= 9 to work correctly. If"
            + " you are not using ReflectionAccessFilter, report this to the Gson maintainers.",
        exception);
  }

  private static RuntimeException createExceptionForRecordReflectionException(
      ReflectiveOperationException exception) {
    throw new RuntimeException(
        "Unexpected ReflectiveOperationException occurred"
            + " (Gson "
            + GsonBuildConfig.VERSION
            + ")."
            + " To support Java records, reflection is utilized to read out information"
            + " about records. All these invocations happens after it is established"
            + " that records exist in the JVM. This exception is unexpected behavior.",
        exception);
  }

  /** Internal abstraction over reflection when Records are supported. */
  private abstract static class RecordHelper {
    abstract boolean isRecord(Class<?> clazz);

    abstract String[] getRecordComponentNames(Class<?> clazz);

    abstract <T> Constructor<T> getCanonicalRecordConstructor(Class<T> raw);

View on GitHub (pinned to 8b8628c656)

Solutions

  1. Ensure you run on a stable JDK that fully supports records (JDK 16+ LTS recommended).
  2. Configure ProGuard/R8 to keep record metadata and Signature attributes.
  3. Register a custom TypeAdapter for the affected record type to avoid Gson's record reflection.
  4. Report the bug to Gson with the full stack trace and the GsonBuildConfig.VERSION from the message.

Example fix

// before: record deserialization fails on a stripped JVM
record Point(int x, int y) {}
Point p = gson.fromJson("{\"x\":1,\"y\":2}", Point.class);

// after: custom adapter bypasses record reflection
Gson gson = new GsonBuilder().registerTypeAdapter(Point.class, new TypeAdapter<Point>() {
    @Override public Point read(JsonReader in) throws IOException {
        in.beginObject(); int x=0,y=0;
        while (in.hasNext()) switch (in.nextName()) { case "x": x=in.nextInt(); break; case "y": y=in.nextInt(); break; default: in.skipValue(); }
        in.endObject(); return new Point(x,y);
    }
    @Override public void write(JsonWriter out, Point v) throws IOException { out.beginObject().name("x").value(v.x()).name("y").value(v.y()).endObject(); }
}).create();
Defensive patterns

Strategy: try-catch

Validate before calling

boolean jvmSupportsRecordsFully() {
  try {
    Class<?> c = Class.forName("java.lang.Record");
    return c != null;
  } catch (ClassNotFoundException e) { return false; }
}

Type guard

static boolean isStableRecordJvm() {
  String v = System.getProperty("java.version");
  return v != null && !v.toLowerCase().contains("ea") && Runtime.version().feature() >= 16;
}

Try / catch

try {
  T obj = gson.fromJson(json, type);
} catch (RuntimeException e) {
  if (e.getMessage().contains("Unexpected ReflectiveOperationException")) {
    // register a custom adapter for the record type and retry
  } else throw e;
}

Prevention

When it happens

Trigger: Deserializing a Java record on a JVM that reported record support but where subsequent record reflection fails, e.g. a non-standard/JDK-buggy runtime, an Android runtime with partial record support, or a tool that strips record metadata.

Common situations: Tooling/bytecode manipulation (ProGuard, JaCoCo, mock frameworks) removing record attributes, early-access JDKs, or non-Oracle JVMs with incomplete record support.

Related errors


AI-assisted analysis of google/gson@8b8628c656 (2026-08-04). Data as JSON: /data/errors/75b411451aab7b3c.json. Report an issue: GitHub.