google/gson · error · IllegalArgumentException
types and labels must be unique
Error message
types and labels must be unique
What it means
Thrown by RuntimeTypeAdapterFactory.registerSubtype(Class, String) when the subtype class or the label string has already been registered on the same factory. The factory keeps two maps (subtypeToLabel and labelToSubtype) and rejects any duplicate on either side to keep the type<->label mapping strictly one-to-one. This is a configuration-time IllegalArgumentException, fired before any serialization occurs.
Solutions
- Search your registration calls for the duplicated class or label name reported in the failure.
- If two classes share a simple name, pass an explicit unique label as the second argument to registerSubtype.
- Make sure the RuntimeTypeAdapterFactory is constructed fresh per GsonBuilder.create() and not reused/shared across builds.
- Build the label set programmatically and assert uniqueness before registering (e.g. collect labels into a Set).
Example fix
// before factory.registerSubtype(Circle.class); factory.registerSubtype(Circle.class); // duplicate class -> throws // after factory.registerSubtype(Circle.class, "circle"); factory.registerSubtype(Square.class, "square");
Defensive patterns
Strategy: validation
Validate before calling
// Before building the factory, assert no duplicate class or label
Set<Class<?>> classes = new HashSet<>();
Set<String> labels = new HashSet<>();
for (var reg : registrations) {
if (!classes.add(reg.type) || !labels.add(reg.label)) {
throw new IllegalStateException("Duplicate subtype registration: " + reg);
}
}
// then safely call factory.registerSubtype(reg.type, reg.label) Prevention
- Construct a fresh RuntimeTypeAdapterFactory per Gson build; do not reuse the same instance across create() calls.
- Prefer explicit labels over simple names when classes may share names.
- Keep all registerSubtype calls in one place adjacent to the type hierarchy.
When it happens
Trigger: Calling registerSubtype(SameClass, ...) twice; calling registerSubtype(ClassA, "foo") and registerSubtype(ClassB, "foo") (duplicate label); calling the single-arg registerSubtype(Class) for two classes whose getSimpleName() collide; re-registering after reusing one factory instance across builds.
Common situations: Two inner classes share a simple name (e.g. two Foo$Bar); a label typo reuses an existing discriminator; building the factory inside a loop or helper that runs more than once; copy-pasting registration blocks; mixing label-based and simple-name registration.
Related errors
- cannot deserialize because it does not define a field named
- cannot deserialize subtype named ; did you forget to…
- cannot serialize because it already defines a field named
- cannot serialize ; did you forget to register a subtype?
- Cannot set value of 'static final
AI-assisted analysis of google/gson@310ac341f2 (2026-08-10).
Data as JSON: /api/errors/04a579d7e7ab3dc8.
Report an issue: GitHub.
Appendix: source
Thrown at extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java:224
@CanIgnoreReturnValue
public RuntimeTypeAdapterFactory<T> recognizeSubtypes() {
this.recognizeSubtypes = true;
return this;
}
/**
* Registers {@code type} identified by {@code label}. Labels are case sensitive.
*
* @throws IllegalArgumentException if either {@code type} or {@code label} have already been
* registered on this type adapter.
*/
@CanIgnoreReturnValue
public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type, String label) {
if (type == null || label == null) {
throw new NullPointerException();
}
if (subtypeToLabel.containsKey(type) || labelToSubtype.containsKey(label)) {
throw new IllegalArgumentException("types and labels must be unique");
}
labelToSubtype.put(label, type);
subtypeToLabel.put(type, label);
return this;
}
/**
* Registers {@code type} identified by its {@link Class#getSimpleName simple name}. Labels are
* case sensitive.
*
* @throws IllegalArgumentException if either {@code type} or its simple name have already been
* registered on this type adapter.
*/
@CanIgnoreReturnValue
public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type) {
return registerSubtype(type, type.getSimpleName());
}
View on GitHub (pinned to 310ac341f2)