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

  1. Register the missing subtype on the consumer's factory: factory.registerSubtype(Triangle.class, "Triangle").
  2. Compare the exact label string in the JSON against the registered labels (case-sensitive) and fix any mismatch.
  3. If accepting untrusted JSON, keep this behavior; do NOT auto-register by class name, as the Javadoc warns it is an injection-attack surface.
  4. 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

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


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