google/gson · error · JsonParseException

cannot serialize ${srcType.getName()} because it already def

Error message

cannot serialize ${srcType.getName()} because it already defines a field named ${typeFieldName}

What it means

Thrown during serialization by RuntimeTypeAdapterFactory (when maintainType is false) if the subtype being serialized already serializes a field whose name collides with the configured typeFieldName. Because the factory prepends its own discriminator field with that exact name, a collision would overwrite or shadow the real field, so it aborts with a JsonParseException. It protects data integrity of the discriminator versus a model field.

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 310ac341f2)

Solutions

  1. Pick a typeFieldName that does not collide with any serialized field, e.g. RuntimeTypeAdapterFactory.of(Base.class, "__type").
  2. Rename or @SerializedName the conflicting domain field to a non-colliding key.
  3. If you must keep the same name, use of(Base.class, typeFieldName, true) (maintainType=true) so the factory does not inject/clone the discriminator.

Example fix

// before
class Event { String type; } // collides with default discriminator
RuntimeTypeAdapterFactory.of(Event.class); // "type"

// after
RuntimeTypeAdapterFactory.of(Event.class, "@type");
Defensive patterns

Strategy: validation

Validate before calling

// Detect field-name collision with the discriminator up front
String typeFieldName = "type";
for (Field f : subtypeClass.getDeclaredFields()) {
  String jsonName = f.isAnnotationPresent(SerializedName.class)
      ? f.getAnnotation(SerializedName.class).value() : f.getName();
  if (jsonName.equals(typeFieldName)) {
    throw new IllegalStateException("Field '" + jsonName + "' collides with discriminator");
  }
}

Try / catch

try { gson.toJson(obj, Shape.class); }
catch (JsonParseException e) {
  if (e.getMessage().contains("already defines a field named")) {
    // switch typeFieldName or rename the field
  } else throw e;
}

Prevention

When it happens

Trigger: The subtype class declares a field literally named "type" (the default discriminator); typeFieldName was customized to a name that the model already uses (e.g. "kind", "id"); a parent class introduces a field that collides after the factory was configured.

Common situations: Default typeFieldName "type" clashes with a domain field; renaming the discriminator to something already used elsewhere in the model; inheriting a base class that already has the conflicting field.

Related errors


AI-assisted analysis of google/gson@310ac341f2 (2026-08-10). Data as JSON: /api/errors/08ee0d763321b724. Report an issue: GitHub.