{"id":"8837930793cc2c9a","repo":"google/gson","slug":"cannot-deserialize-basetype-subtype-named-label","errorCode":null,"errorMessage":"cannot deserialize {baseType} subtype named {label}; did you forget to register a subtype?","messagePattern":"cannot deserialize (.+?) subtype named (.+?); did you forget to register a subtype\\?","errorType":"exception","errorClass":"JsonParseException","httpStatus":null,"severity":"error","filePath":"extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java","lineNumber":286,"sourceCode":"        JsonElement labelJsonElement;\n        if (maintainType) {\n          labelJsonElement = jsonElement.getAsJsonObject().get(typeFieldName);\n        } else {\n          labelJsonElement = jsonElement.getAsJsonObject().remove(typeFieldName);\n        }\n\n        if (labelJsonElement == null) {\n          throw new JsonParseException(\n              \"cannot deserialize \"\n                  + baseType\n                  + \" because it does not define a field named \"\n                  + typeFieldName);\n        }\n        String label = labelJsonElement.getAsString();\n        @SuppressWarnings(\"unchecked\") // registration requires that subtype extends T\n        TypeAdapter<R> delegate = (TypeAdapter<R>) labelToDelegate.get(label);\n        if (delegate == null) {\n          throw new JsonParseException(\n              \"cannot deserialize \"\n                  + baseType\n                  + \" subtype named \"\n                  + label\n                  + \"; did you forget to register a subtype?\");\n        }\n        return delegate.fromJsonTree(jsonElement);\n      }\n\n      @Override\n      public void write(JsonWriter out, R value) throws IOException {\n        Class<?> srcType = value.getClass();\n        String label = subtypeToLabel.get(srcType);\n        @SuppressWarnings(\"unchecked\") // registration requires that subtype extends T\n        TypeAdapter<R> delegate = (TypeAdapter<R>) subtypeToDelegate.get(srcType);\n        if (delegate == null) {\n          throw new JsonParseException(\n              \"cannot serialize \" + srcType.getName() + \"; did you forget to register a subtype?\");","sourceCodeStart":268,"sourceCodeEnd":304,"githubUrl":"https://github.com/google/gson/blob/8b8628c65699bc4421696183c62ae0c1b9b281dc/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java#L268-L304","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: JSON has \"type\": \"Triangle\" but Triangle is not registered\nRuntimeTypeAdapterFactory<Shape> f = RuntimeTypeAdapterFactory.of(Shape.class)\n    .registerSubtype(Circle.class)\n    .registerSubtype(Rectangle.class);\nShape s = gson.fromJson(json, Shape.class); // throws\n\n// after: register the missing subtype\nRuntimeTypeAdapterFactory<Shape> f = RuntimeTypeAdapterFactory.of(Shape.class)\n    .registerSubtype(Circle.class)\n    .registerSubtype(Rectangle.class)\n    .registerSubtype(Triangle.class, \"Triangle\");","handlingStrategy":"validation","validationCode":"// Validate the label is in the registered set before deserializing\nString typeFieldName = \"type\";\nSet<String> allowed = Set.of(\"Circle\", \"Rectangle\", \"Diamond\");\nJsonElement root = JsonParser.parseString(json);\nString label = root.getAsJsonObject().get(typeFieldName).getAsString();\nif (!allowed.contains(label)) {\n  throw new IllegalArgumentException(\"Unknown subtype label: \" + label);\n}\nShape s = gson.fromJson(root, Shape.class);","typeGuard":"static boolean isRegisteredLabel(String json, String typeFieldName, Set<String> allowed) {\n  try {\n    JsonElement e = JsonParser.parseString(json);\n    return e.isJsonObject() && allowed.contains(e.getAsJsonObject().get(typeFieldName).getAsString());\n  } catch (Exception ex) { return false; }\n}","tryCatchPattern":"try {\n  Shape s = gson.fromJson(json, Shape.class);\n} catch (JsonParseException e) {\n  if (e.getMessage().contains(\"did you forget to register a subtype?\")) {\n    // unknown/untrusted label: reject payload, log the label\n  } else throw e;\n}","preventionTips":["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."],"tags":["runtime-type-adapter","polymorphic","deserialization","unregistered-subtype","untrusted-input"],"analyzedSha":"8b8628c65699bc4421696183c62ae0c1b9b281dc","analyzedAt":"2026-08-04T19:12:22.202Z","schemaVersion":2}