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

  1. Choose a typeFieldName that does not collide with any subtype field, e.g. of(Shape.class, "@type") or "_type".
  2. Rename or exclude the conflicting field on the subtype class (e.g. @SerializedName to a different name, or transient).
  3. 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

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


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