{"id":"75b411451aab7b3c","repo":"google/gson","slug":"unexpected-reflectiveoperationexception-occurred","errorCode":null,"errorMessage":"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.","messagePattern":"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\\.","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/8b8628c65699bc4421696183c62ae0c1b9b281dc/gson/src/main/java/com/google/gson/internal/reflect/ReflectionHelper.java#L193-L229","documentation":"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.","triggerScenarios":"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.","commonSituations":"Tooling/bytecode manipulation (ProGuard, JaCoCo, mock frameworks) removing record attributes, early-access JDKs, or non-Oracle JVMs with incomplete record support.","solutions":["Ensure you run on a stable JDK that fully supports records (JDK 16+ LTS recommended).","Configure ProGuard/R8 to keep record metadata and Signature attributes.","Register a custom TypeAdapter for the affected record type to avoid Gson's record reflection.","Report the bug to Gson with the full stack trace and the GsonBuildConfig.VERSION from the message."],"exampleFix":"// before: record deserialization fails on a stripped JVM\nrecord Point(int x, int y) {}\nPoint p = gson.fromJson(\"{\\\"x\\\":1,\\\"y\\\":2}\", Point.class);\n\n// after: custom adapter bypasses record reflection\nGson gson = new GsonBuilder().registerTypeAdapter(Point.class, new TypeAdapter<Point>() {\n    @Override public Point read(JsonReader in) throws IOException {\n        in.beginObject(); int x=0,y=0;\n        while (in.hasNext()) switch (in.nextName()) { case \"x\": x=in.nextInt(); break; case \"y\": y=in.nextInt(); break; default: in.skipValue(); }\n        in.endObject(); return new Point(x,y);\n    }\n    @Override public void write(JsonWriter out, Point v) throws IOException { out.beginObject().name(\"x\").value(v.x()).name(\"y\").value(v.y()).endObject(); }\n}).create();","handlingStrategy":"try-catch","validationCode":"boolean jvmSupportsRecordsFully() {\n  try {\n    Class<?> c = Class.forName(\"java.lang.Record\");\n    return c != null;\n  } catch (ClassNotFoundException e) { return false; }\n}","typeGuard":"static boolean isStableRecordJvm() {\n  String v = System.getProperty(\"java.version\");\n  return v != null && !v.toLowerCase().contains(\"ea\") && Runtime.version().feature() >= 16;\n}","tryCatchPattern":"try {\n  T obj = gson.fromJson(json, type);\n} catch (RuntimeException e) {\n  if (e.getMessage().contains(\"Unexpected ReflectiveOperationException\")) {\n    // register a custom adapter for the record type and retry\n  } else throw e;\n}","preventionTips":["Run on a stable JDK (>=16) for record support.","Keep record metadata with ProGuard/R8 rules.","Provide custom adapters for records in tooling-heavy environments."],"tags":["gson","reflection","java-records","java-version","bug-report"],"analyzedSha":"8b8628c65699bc4421696183c62ae0c1b9b281dc","analyzedAt":"2026-08-04T19:12:22.202Z","schemaVersion":2}