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
- Ensure the record class is unmodified by bytecode rewriting; exclude it from obfuscation/proguard.
- Run on a stable JDK (17+) with full final record support rather than a preview.
- Register a custom TypeAdapter for the affected record to bypass the reflective record path.
- 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
- Exclude record classes from obfuscation/proguard renaming.
- Validate record metadata alignment in a startup smoke test.
- Prefer stable JDK 17+ when using records with Gson.
- Register explicit TypeAdapters for records produced by bytecode tools.
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
- Primitive type is not allowed
- Must specify owner type for {rawType}
- At most one lower bound is supported
- Exactly one upper bound must be specified
- When lower bound is specified, upper bound must be Object
AI-assisted analysis of google/gson@8b8628c656 (2026-08-04).
Data as JSON: /data/errors/870b6acd079d148e.json.
Report an issue: GitHub.