google/gson · error · JsonParseException
cannot serialize {srcType.getName()} because it already defi
Error message
cannot serialize {srcType.getName()} because it already defines a field named {typeFieldName} What it means
Thrown during serialization by RuntimeTypeAdapterFactory's write() when the subtype's own serialized fields already include a key equal to the configured typeFieldName, and maintainType is false (the default). Because the adapter needs to prepend the discriminator field to the cloned object, a pre-existing field with the same name would collide. This is a JsonParseException.
Source
Thrown at extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java:316
Class<?> srcType = value.getClass();
String label = subtypeToLabel.get(srcType);
@SuppressWarnings("unchecked") // registration requires that subtype extends T
TypeAdapter<R> delegate = (TypeAdapter<R>) subtypeToDelegate.get(srcType);
if (delegate == null) {
throw new JsonParseException(
"cannot serialize " + srcType.getName() + "; did you forget to register a subtype?");
}
JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject();
if (maintainType) {
jsonElementAdapter.write(out, jsonObject);
return;
}
JsonObject clone = new JsonObject();
if (jsonObject.has(typeFieldName)) {
throw new JsonParseException(
"cannot serialize "
+ srcType.getName()
+ " because it already defines a field named "
+ typeFieldName);
}
clone.add(typeFieldName, new JsonPrimitive(label));
for (Map.Entry<String, JsonElement> e : jsonObject.entrySet()) {
clone.add(e.getKey(), e.getValue());
}
jsonElementAdapter.write(out, clone);
}
}.nullSafe();
}
}
View on GitHub (pinned to 8b8628c656)
Solutions
- Choose a typeFieldName that does not collide with any subtype field, e.g. of(Shape.class, "@type") or "_type".
- Rename or exclude the conflicting field on the subtype class (e.g. @SerializedName to a different name, or transient).
- If you want the subtype's own field preserved verbatim, use of(Shape.class, "type", true) (maintainType=true) which skips the prepend logic, but then you are responsible for the discriminator on the producer side.
Example fix
// before: Shape subclass has a field named "type"
class Node { String type; int value; }
RuntimeTypeAdapterFactory<Node> f = RuntimeTypeAdapterFactory.of(Node.class) // typeFieldName defaults to "type"
.registerSubtype(Node.class, "leaf");
gson.toJson(node, Node.class); // throws: already defines "type"
// after: use a non-colliding discriminator name
RuntimeTypeAdapterFactory<Node> f = RuntimeTypeAdapterFactory.of(Node.class, "@type")
.registerSubtype(Node.class, "leaf"); Defensive patterns
Strategy: validation
Validate before calling
// Pick a discriminator name that does not collide with any subtype field
String typeFieldName = "@type"; // or "_type"
// Verify no subtype declares a field/serializedName equal to typeFieldName
List<Class<?>> subtypes = List.of(Node.class, Leaf.class);
for (Class<?> c : subtypes) {
for (Field f : c.getDeclaredFields()) {
if (typeFieldName.equals(f.getName())) throw new IllegalStateException("Collision on " + typeFieldName);
}
}
RuntimeTypeAdapterFactory<Node> f = RuntimeTypeAdapterFactory.of(Node.class, typeFieldName); Type guard
static boolean discriminatorCollides(String typeFieldName, List<Class<?>> subtypes) {
return subtypes.stream().flatMap(c -> Arrays.stream(c.getDeclaredFields()))
.anyMatch(f -> typeFieldName.equals(f.getName()));
} Prevention
- Choose a discriminator name unlikely to collide, e.g. "@type" or "_type".
- Audit subtype fields (and @SerializedName values) for collisions before wiring the factory.
- If the subtype legitimately owns the field, consider maintainType=true and manage the discriminator yourself.
- Add a unit test serializing each subtype to confirm no collision at runtime.
When it happens
Trigger: The subtype class (or its serialized form) has a field literally named "type" (the default typeFieldName), or you configured of(Shape.class, "kind") but the class already has a field named "kind"; a custom type adapter or @SerializedName produces a JSON member matching the discriminator name; maintainType=false (default) so the adapter tries to add the field itself.
Common situations: Domain models with a natural "type" field (common in tagged unions, AST nodes, discriminated unions); ORM entities that expose a type discriminator column; renaming the typeFieldName to something that happens to collide with an existing field; using a custom serializer that adds extra keys.
Related errors
- cannot serialize {srcType.getName()}; did you forget to regi
- types and labels must be unique
- cannot deserialize {baseType} because it does not define a f
- cannot deserialize {baseType} subtype named {label}; did you
- Deserialization is unsupported
AI-assisted analysis of google/gson@8b8628c656 (2026-08-04).
Data as JSON: /data/errors/ee168c75da16d8c5.json.
Report an issue: GitHub.