google/gson · error · JsonParseException
cannot deserialize {baseType} subtype named {label}; did you
Error message
cannot deserialize {baseType} subtype named {label}; did you forget to register a subtype? What it means
Thrown during deserialization by RuntimeTypeAdapterFactory's read() when the JSON object DOES contain the discriminator field, but its label value does not match any subtype registered via registerSubtype. The factory only deserializes labels it was explicitly taught, both as a correctness guarantee and as a defense against subtype-injection attacks. This is a JsonParseException.
Source
Thrown at extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java:286
JsonElement labelJsonElement;
if (maintainType) {
labelJsonElement = jsonElement.getAsJsonObject().get(typeFieldName);
} else {
labelJsonElement = jsonElement.getAsJsonObject().remove(typeFieldName);
}
if (labelJsonElement == null) {
throw new JsonParseException(
"cannot deserialize "
+ baseType
+ " because it does not define a field named "
+ typeFieldName);
}
String label = labelJsonElement.getAsString();
@SuppressWarnings("unchecked") // registration requires that subtype extends T
TypeAdapter<R> delegate = (TypeAdapter<R>) labelToDelegate.get(label);
if (delegate == null) {
throw new JsonParseException(
"cannot deserialize "
+ baseType
+ " subtype named "
+ label
+ "; did you forget to register a subtype?");
}
return delegate.fromJsonTree(jsonElement);
}
@Override
public void write(JsonWriter out, R value) throws IOException {
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?");View on GitHub (pinned to 8b8628c656)
Solutions
- Register the missing subtype on the consumer's factory: factory.registerSubtype(Triangle.class, "Triangle").
- Compare the exact label string in the JSON against the registered labels (case-sensitive) and fix any mismatch.
- If accepting untrusted JSON, keep this behavior; do NOT auto-register by class name, as the Javadoc warns it is an injection-attack surface.
- Synchronize subtype registration across services (shared module defining the factory) to avoid version skew.
Example fix
// before: JSON has "type": "Triangle" but Triangle is not registered
RuntimeTypeAdapterFactory<Shape> f = RuntimeTypeAdapterFactory.of(Shape.class)
.registerSubtype(Circle.class)
.registerSubtype(Rectangle.class);
Shape s = gson.fromJson(json, Shape.class); // throws
// after: register the missing subtype
RuntimeTypeAdapterFactory<Shape> f = RuntimeTypeAdapterFactory.of(Shape.class)
.registerSubtype(Circle.class)
.registerSubtype(Rectangle.class)
.registerSubtype(Triangle.class, "Triangle"); Defensive patterns
Strategy: validation
Validate before calling
// Validate the label is in the registered set before deserializing
String typeFieldName = "type";
Set<String> allowed = Set.of("Circle", "Rectangle", "Diamond");
JsonElement root = JsonParser.parseString(json);
String label = root.getAsJsonObject().get(typeFieldName).getAsString();
if (!allowed.contains(label)) {
throw new IllegalArgumentException("Unknown subtype label: " + label);
}
Shape s = gson.fromJson(root, Shape.class); Type guard
static boolean isRegisteredLabel(String json, String typeFieldName, Set<String> allowed) {
try {
JsonElement e = JsonParser.parseString(json);
return e.isJsonObject() && allowed.contains(e.getAsJsonObject().get(typeFieldName).getAsString());
} catch (Exception ex) { return false; }
} Try / catch
try {
Shape s = gson.fromJson(json, Shape.class);
} catch (JsonParseException e) {
if (e.getMessage().contains("did you forget to register a subtype?")) {
// unknown/untrusted label: reject payload, log the label
} else throw e;
} Prevention
- Keep the producer and consumer subtype registrations synchronized (shared module).
- Remember labels are case-sensitive; normalize case if your source is sloppy.
- Do NOT auto-register by class name to bypass this; the Javadoc warns it is an injection-attack surface.
- Maintain an allowed-label whitelist and validate untrusted JSON against it before deserialization.
When it happens
Trigger: JSON carries "type": "Triangle" but only Circle/Rectangle/Diamond were registered; producer registered a subtype the consumer did not; the label string differs by case or whitespace ("diamond" vs "Diamond"); a malicious or third-party payload injects an unregistered label.
Common situations: Version skew: a newer producer added a subtype but the consumer is on an older build; case-sensitivity mistakes (labels are case sensitive per the Javadoc); trailing whitespace or invisible characters in the label; copy-paste of label strings with typos; untrusted input reaching the deserializer.
Related errors
- cannot deserialize {baseType} because it does not define a f
- cannot serialize {srcType.getName()}; did you forget to regi
- types and labels must be unique
- cannot serialize {srcType.getName()} because it already defi
- Type adapter '{typeAdapter}' returned wrong type; requested
AI-assisted analysis of google/gson@8b8628c656 (2026-08-04).
Data as JSON: /data/errors/8837930793cc2c9a.json.
Report an issue: GitHub.