{"record":{"id":"6b7b1cf21946194a","repo":"google/gson","slug":"unexpected-reflectiveoperationexception-occurred","errorCode":null,"errorMessage":"Unexpected ReflectiveOperationException occurred (Gson {}). 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.","messagePattern":"Unexpected ReflectiveOperationException occurred \\(Gson (.+?)\\)\\. 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\\.","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"gson/src/main/java/com/google/gson/internal/reflect/ReflectionHelper.java","lineNumber":211,"sourceCode":"  }\n\n  public static <T> Constructor<T> getCanonicalRecordConstructor(Class<T> raw) {\n    return RECORD_HELPER.getCanonicalRecordConstructor(raw);\n  }\n\n  public static RuntimeException createExceptionForUnexpectedIllegalAccess(\n      IllegalAccessException exception) {\n    throw new RuntimeException(\n        \"Unexpected IllegalAccessException occurred (Gson \"\n            + GsonBuildConfig.VERSION\n            + \"). Certain ReflectionAccessFilter features require Java >= 9 to work correctly. If\"\n            + \" you are not using ReflectionAccessFilter, report this to the Gson maintainers.\",\n        exception);\n  }\n\n  private static RuntimeException createExceptionForRecordReflectionException(\n      ReflectiveOperationException exception) {\n    throw new RuntimeException(\n        \"Unexpected ReflectiveOperationException occurred\"\n            + \" (Gson \"\n            + GsonBuildConfig.VERSION\n            + \").\"\n            + \" To support Java records, reflection is utilized to read out information\"\n            + \" about records. All these invocations happens after it is established\"\n            + \" that records exist in the JVM. This exception is unexpected behavior.\",\n        exception);\n  }\n\n  /** Internal abstraction over reflection when Records are supported. */\n  private abstract static class RecordHelper {\n    abstract boolean isRecord(Class<?> clazz);\n\n    abstract String[] getRecordComponentNames(Class<?> clazz);\n\n    abstract <T> Constructor<T> getCanonicalRecordConstructor(Class<T> raw);\n","sourceCodeStart":193,"sourceCodeEnd":229,"githubUrl":"https://github.com/google/gson/blob/310ac341f2f92a454b229bf21f70d2d18b2b6db7/gson/src/main/java/com/google/gson/internal/reflect/ReflectionHelper.java#L193-L229","documentation":"Thrown by ReflectionHelper when record-related reflection (Class.isRecord, RecordComponent access, canonical constructor lookup, accessor method invocation) fails with ReflectiveOperationException after the JVM was detected to support records. The code path only runs on Java 14+ (records preview) or 16+ (stable); failure here is unexpected and indicates a JVM or toolchain anomaly.","triggerScenarios":"Deserializing or serializing a Java record on a JVM that reported records as supported, but a subsequent reflective call (getting record components, canonical constructor, or accessor method) throws. Seen with non-standard JVMs, bytecode manipulation tools (some older Lombok/MapStruct versions), partial record support, or future/preview record features.","commonSituations":"Preview record features on early JDK builds; class file transformations that strip Record attributes; custom classloaders hiding record metadata; Kotlin/Scala data classes masquerading as records; running on a JVM whose java.lang.reflect.RecordComponents API is inconsistent with the actual bytecode.","solutions":["Run on a stable Java LTS (17 or 21) where records are fully supported and stable.","Ensure no bytecode tooling (old Lombok, old ASM/ByteBuddy, obfuscators) strips the Record attribute or RecordComponent metadata from the compiled class.","If the type is not truly a record, refactor it to a plain class so Gson does not take the record reflection path.","Register a custom TypeAdapter<YourRecord> that constructs the record via its canonical constructor, bypassing record reflection.","Report the failure to Gson maintainers with the JVM vendor/version and the record class, as the message indicates this is unexpected."],"exampleFix":"// before: record deserialized via reflection on a flaky JVM\npublic record Point(int x, int y) {}\nPoint p = gson.fromJson(\"{\\\"x\\\":1,\\\"y\\\":2}\", Point.class); // throws\n\n// after: custom adapter using canonical constructor directly\nGson g = new GsonBuilder()\n  .registerTypeAdapter(Point.class, new TypeAdapter<Point>() {\n    public Point read(JsonReader in) throws IOException {\n      in.beginObject(); int x=0,y=0;\n      while (in.hasNext()) {\n        switch (in.nextName()) {\n          case \"x\": x = in.nextInt(); break;\n          case \"y\": y = in.nextInt(); break;\n          default: in.skipValue();\n        }\n      }\n      in.endObject();\n      return new Point(x, y);\n    }\n    public void write(JsonWriter out, Point p) throws IOException {\n      out.beginObject().name(\"x\").value(p.x()).name(\"y\").value(p.y()).endObject();\n    }\n  }).create();","handlingStrategy":"fallback","validationCode":"// detect record metadata stability at startup\ntry {\n  Class.forName(\"java.lang.reflect.RecordComponent\");\n  // smoke test a real record round-trip\n  Object r = gson.fromJson(\"{\\\"x\\\":1,\\\"y\\\":2}\", com.example.Point.class);\n} catch (Throwable t) {\n  throw new IllegalStateException(\"Record reflection unstable on this JVM/toolchain\", t);\n}","typeGuard":"public static boolean isStableRecord(Class<?> c) {\n  if (!c.isRecord()) return false;\n  try { c.getRecordComponents(); c.getDeclaredConstructor(\n      java.util.Arrays.stream(c.getRecordComponents())\n        .map(rc -> rc.getType()).toArray(Class<?>[]::new)); return true; }\n  catch (Throwable t) { return false; }\n}","tryCatchPattern":"try {\n  return gson.fromJson(json, Point.class);\n} catch (RuntimeException e) {\n  if (e.getMessage() != null && e.getMessage().contains(\"Unexpected ReflectiveOperationException occurred\")) {\n    // fall back to a custom TypeAdapter<YourRecord> using the canonical constructor\n    return customPointGson().fromJson(json, Point.class);\n  }\n  throw e;\n}","preventionTips":["Run on a stable Java LTS (17/21) for records.","Ensure bytecode tooling (Lombok, ASM, obfuscators) preserves Record attributes.","Supply custom TypeAdapters for records in critical paths to avoid reflection reliance.","Smoke-test record round-trip in CI on the production JVM."],"tags":["gson","reflection","java-records","runtime-exception","jvm"],"backgroundTag":null,"analyzedSha":"310ac341f2f92a454b229bf21f70d2d18b2b6db7","analyzedAt":"2026-08-10T02:58:47.455Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}