google/gson · error · IllegalStateException

Could not find the index in the constructor '" + ReflectionH

Error message

Could not find the index in the constructor '" + ReflectionHelper.constructorToString(constructor) + "' for field with name '" + field.fieldName + "', unable to determine which argument in the constructor the field corresponds to. This is unexpected behavior, as we expect the RecordComponents to have the same names as the fields in the Java class, and that the order of the RecordComponents is the same as the order of the canonical constructor parameters.

What it means

Thrown by RecordAdapter.readField when a record field name has no matching entry in componentIndices, meaning the field's name does not line up with any record component name obtained from ReflectionHelper.getRecordComponentNames. This indicates the JVM reflection metadata for the record is inconsistent with what Gson expects (component names == field names == constructor params). It is an IllegalStateException treated as an internal/contract failure.

Source

Thrown at gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java:631

      zeroes.put(long.class, 0L);
      zeroes.put(float.class, 0F);
      zeroes.put(double.class, 0D);
      zeroes.put(char.class, '\0');
      zeroes.put(boolean.class, false);
      return zeroes;
    }

    @Override
    Object[] createAccumulator() {
      return constructorArgsDefaults.clone();
    }

    @Override
    void readField(Object[] accumulator, JsonReader in, BoundField field) throws IOException {
      // Obtain the component index from the name of the field backing it
      Integer componentIndex = componentIndices.get(field.fieldName);
      if (componentIndex == null) {
        throw new IllegalStateException(
            "Could not find the index in the constructor '"
                + ReflectionHelper.constructorToString(constructor)
                + "' for field with name '"
                + field.fieldName
                + "', unable to determine which argument in the constructor the field corresponds"
                + " to. This is unexpected behavior, as we expect the RecordComponents to have the"
                + " same names as the fields in the Java class, and that the order of the"
                + " RecordComponents is the same as the order of the canonical constructor"
                + " parameters.");
      }
      field.readIntoArray(in, componentIndex, accumulator);
    }

    @Override
    T finalize(Object[] accumulator) {
      try {
        return constructor.newInstance(accumulator);
      } catch (IllegalAccessException e) {

View on GitHub (pinned to 8b8628c656)

Solutions

  1. Ensure the record class is unmodified by bytecode rewriting; exclude it from obfuscation/proguard.
  2. Run on a stable JDK (17+) with full final record support rather than a preview.
  3. Register a custom TypeAdapter for the affected record to bypass the reflective record path.
  4. If using bytecode tools, ensure they preserve RecordComponent name alignment.

Example fix

// before: ProGuard renames record components
-keep class com.example.** { *; } // too late, records already mangled

// after: keep record metadata intact
-keep,allowobfuscation class com.example.records.** { *; }
// or register adapter
.registerTypeAdapter(MyRecord.class, new MyRecordAdapter())
Defensive patterns

Strategy: validation

Validate before calling

// Sanity-check record component/field name alignment once at startup
String[] comps = ReflectionHelper.getRecordComponentNames(MyRecord.class);
Set<String> compNames = new HashSet<>(Arrays.asList(comps));
for (Field f : MyRecord.class.getDeclaredFields()) {
  if (!Modifier.isStatic(f.getModifiers()) && !compNames.contains(f.getName())) {
    throw new IllegalStateException("Record metadata inconsistent for " + f);
  }
}

Type guard

null

Try / catch

try {
  gson.fromJson(json, MyRecord.class);
} catch (IllegalStateException e) {
  if (e.getMessage().contains("Could not find the index in the constructor")) {
    // likely bytecode rewriting; use a custom adapter
  } else throw e;
}

Prevention

When it happens

Trigger: Occurs when a record has been transformed (by bytecode rewriter, Lombok-like tool, obfuscator, or annotation processor) so field names diverge from RecordComponent names, or when running on a non-standard JVM with incomplete record support. Also reachable if a custom TypeAdapter or @JsonAdapter injects a mismatched BoundField.

Common situations: Obfuscators/proguards renaming record fields without updating component metadata; older/preview JVM record implementations; bytecode manipulation libraries (ASM/ByteBuddy) that add fields to records; Kotlin data classes misinterpreted as records on some JVM versions.

Related errors


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