{"id":"04a579d7e7ab3dc8","repo":"google/gson","slug":"types-and-labels-must-be-unique","errorCode":null,"errorMessage":"types and labels must be unique","messagePattern":"types and labels must be unique","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java","lineNumber":224,"sourceCode":"  @CanIgnoreReturnValue\n  public RuntimeTypeAdapterFactory<T> recognizeSubtypes() {\n    this.recognizeSubtypes = true;\n    return this;\n  }\n\n  /**\n   * Registers {@code type} identified by {@code label}. Labels are case sensitive.\n   *\n   * @throws IllegalArgumentException if either {@code type} or {@code label} have already been\n   *     registered on this type adapter.\n   */\n  @CanIgnoreReturnValue\n  public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type, String label) {\n    if (type == null || label == null) {\n      throw new NullPointerException();\n    }\n    if (subtypeToLabel.containsKey(type) || labelToSubtype.containsKey(label)) {\n      throw new IllegalArgumentException(\"types and labels must be unique\");\n    }\n    labelToSubtype.put(label, type);\n    subtypeToLabel.put(type, label);\n    return this;\n  }\n\n  /**\n   * Registers {@code type} identified by its {@link Class#getSimpleName simple name}. Labels are\n   * case sensitive.\n   *\n   * @throws IllegalArgumentException if either {@code type} or its simple name have already been\n   *     registered on this type adapter.\n   */\n  @CanIgnoreReturnValue\n  public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type) {\n    return registerSubtype(type, type.getSimpleName());\n  }\n","sourceCodeStart":206,"sourceCodeEnd":242,"githubUrl":"https://github.com/google/gson/blob/8b8628c65699bc4421696183c62ae0c1b9b281dc/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java#L206-L242","documentation":"Thrown by RuntimeTypeAdapterFactory.registerSubtype() when you attempt to register either a class or a label that has already been registered on the same factory instance. Each subtype class and each label string must map one-to-one; registering Circle twice, or registering two classes under the same label, is rejected because it would make the polymorphic mapping ambiguous. This is a configuration-time IllegalArgumentException, not a serialization error.","triggerScenarios":"Calling factory.registerSubtype(Circle.class, \"Circle\") followed by factory.registerSubtype(Circle.class) (duplicate class), or factory.registerSubtype(Diamond.class, \"Circle\") (duplicate label). Also triggered by the no-arg registerSubtype(type) variant when two classes share the same simple name (e.g., two nested classes both named \"Builder\"), since it defaults the label to type.getSimpleName().","commonSituations":"Copying subtype registration code across modules and double-registering; using the default simple-name label for inner classes with colliding names; programmatic registration in a loop that re-runs (e.g. scanning a package twice); refactoring a class rename but forgetting to update the label while the old label still gets registered elsewhere.","solutions":["Audit every registerSubtype call for this factory and ensure each Class and each label String appears exactly once.","If you use the no-arg registerSubtype(type), check Class.getSimpleName() collisions; switch to the two-arg form with explicit unique labels.","Guard registration with a containsKey check on subtypeToLabel/labelToSubtype if registration may run more than once (e.g. from a plugin loader).","Centralize all subtype registration in one place (a single factory builder method) so duplicates are obvious."],"exampleFix":"// before\nRuntimeTypeAdapterFactory<Shape> f = RuntimeTypeAdapterFactory.of(Shape.class);\nf.registerSubtype(Rectangle.class, \"Rect\");\nf.registerSubtype(Square.class, \"Rect\"); // throws: duplicate label\n\n// after\nf.registerSubtype(Rectangle.class, \"Rect\");\nf.registerSubtype(Square.class, \"Square\");","handlingStrategy":"validation","validationCode":"// Before registering, check both maps for duplicates (reflection or explicit set)\nRuntimeTypeAdapterFactory<Shape> f = RuntimeTypeAdapterFactory.of(Shape.class, \"type\");\nList<Class<?>> types = List.of(Circle.class, Rectangle.class, Circle.class);\nSet<String> usedLabels = new HashSet<>();\nSet<Class<?>> usedTypes = new HashSet<>();\nfor (Class<?> c : types) {\n  String label = c.getSimpleName();\n  if (usedTypes.contains(c) || usedLabels.contains(label)) {\n    // skip or throw a descriptive error BEFORE calling registerSubtype\n    throw new IllegalStateException(\"Duplicate subtype or label: \" + c.getName() + \" / \" + label);\n  }\n  usedTypes.add(c); usedLabels.add(label);\n  f.registerSubtype(c, label);\n}","typeGuard":"// Type guard: narrow to a registered-subtype registry helper\nstatic boolean isRegistrationUnique(Class<?> type, String label,\n    Set<Class<?>> knownTypes, Set<String> knownLabels) {\n  return type != null && label != null\n      && !knownTypes.contains(type) && !knownLabels.contains(label);\n}","tryCatchPattern":null,"preventionTips":["Centralize all registerSubtype calls in one factory-builder method so duplicates are visible.","When using the single-arg registerSubtype(type), remember the label defaults to getSimpleName(); check for simple-name collisions across nested classes.","Build the labelToSubtype map from a single source of truth (e.g. an enum) to avoid drift.","Add a unit test asserting the factory registers every expected subtype exactly once."],"tags":["runtime-type-adapter","polymorphic","configuration","duplicate-registration"],"analyzedSha":"8b8628c65699bc4421696183c62ae0c1b9b281dc","analyzedAt":"2026-08-04T19:12:22.202Z","schemaVersion":2}