google/gson · error · IllegalStateException
Must only create direct subclasses of TypeToken
Error message
Must only create direct subclasses of TypeToken
What it means
Thrown by TypeToken.getTypeTokenTypeArgument when the anonymous subclass's generic superclass is a ParameterizedType but its rawType is NOT TypeToken itself - meaning the user created a subclass of a subclass of TypeToken (e.g. `class MyToken extends TypeToken<List<String>>{}` then `new MyToken(){}`). TypeToken only supports direct subclasses.
Solutions
- Do not subclass TypeToken transitively; instantiate it directly as an anonymous class: `new TypeToken<List<String>>() {}`.
- If you need a reusable handle, hold a TypeToken<?> field initialized with the anonymous form rather than a named subclass.
- For runtime-determined types, use TypeToken.getParameterized(...) or TypeToken.get(Class) instead of a named subclass.
Example fix
// before (broken)
class StringListToken extends TypeToken<List<String>> {}
TypeToken<List<String>> t = new StringListToken(); // throws
// after (direct anonymous subclass)
TypeToken<List<String>> t = new TypeToken<List<String>>() {};
// after (reusable constant)
public final class Tokens {
public static final TypeToken<List<String>> STRING_LIST = new TypeToken<List<String>>() {};
} Defensive patterns
Strategy: validation
Validate before calling
// enforce direct-subclass invariant at build/test time
Class<?> c = token.getClass();
Class<?> sup = c.getSuperclass();
if (sup != TypeToken.class) {
throw new IllegalStateException("TypeToken subclass chain too deep: " + sup);
} Type guard
public static boolean isDirectTypeTokenSubclass(Class<?> c) {
return c.getSuperclass() == TypeToken.class;
} Try / catch
try {
TypeToken<List<String>> t = new StringListToken(); // subclass chain
return gson.fromJson(json, t.getType());
} catch (IllegalStateException e) {
if (e.getMessage().equals("Must only create direct subclasses of TypeToken")) {
// refactor to direct anonymous subclass
TypeToken<List<String>> t2 = new TypeToken<List<String>>() {};
return gson.fromJson(json, t2.getType());
}
throw e;
} Prevention
- Never create named subclasses of TypeToken; always use anonymous direct subclasses.
- Hold reusable TypeTokens as static final fields of direct anonymous instances.
- Use TypeToken.getParameterized when types are runtime-known.
- Code-review TypeToken usages to catch indirect subclassing early.
When it happens
Trigger: Defining `class StringListToken extends TypeToken<List<String>>` and then instantiating it (directly or anonymously) for use with Gson. The constructor's check requires the immediate generic superclass to be TypeToken; any intermediate type breaks this invariant.
Common situations: Refactoring repeated TypeToken usages into a named subclass for reuse; library authors trying to provide pre-baked TypeTokens; framework code that wraps TypeToken; well-intentioned DRY that violates TypeToken's contract.
Related errors
- TypeToken must be created with a type argument: new…
- Must specify owner type for
- Primitive type is not allowed
- rawType must be of type Class, but was
- TypeToken captured `null` as type argument; probably a…
AI-assisted analysis of google/gson@310ac341f2 (2026-08-10).
Data as JSON: /api/errors/196f79be63689f00.
Report an issue: GitHub.
Appendix: source
Thrown at gson/src/main/java/com/google/gson/reflect/TypeToken.java:118
Type typeArgument = GsonTypes.canonicalize(parameterized.getActualTypeArguments()[0]);
if (isCapturingTypeVariablesForbidden()) {
verifyNoTypeVariable(typeArgument);
}
return typeArgument;
}
}
// Check for raw TypeToken as superclass
else if (superclass == TypeToken.class) {
throw new IllegalStateException(
"TypeToken must be created with a type argument: new TypeToken<...>() {}; When using code"
+ " shrinkers (ProGuard, R8, ...) make sure that generic signatures are preserved."
+ "\nSee "
+ TroubleshootingGuide.createUrl("type-token-raw"));
}
// User created subclass of subclass of TypeToken
throw new IllegalStateException("Must only create direct subclasses of TypeToken");
}
private static void verifyNoTypeVariable(Type type) {
if (type instanceof TypeVariable) {
TypeVariable<?> typeVariable = (TypeVariable<?>) type;
throw new IllegalArgumentException(
"TypeToken type argument must not contain a type variable; captured type variable "
+ typeVariable.getName()
+ " declared by "
+ typeVariable.getGenericDeclaration()
+ "\nSee "
+ TroubleshootingGuide.createUrl("typetoken-type-variable"));
} else if (type instanceof GenericArrayType) {
verifyNoTypeVariable(((GenericArrayType) type).getGenericComponentType());
} else if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
Type ownerType = parameterizedType.getOwnerType();
if (ownerType != null) {View on GitHub (pinned to 310ac341f2)