google/gson · error · IllegalArgumentException
TypeToken type argument must not contain a type variable; ca
Error message
TypeToken type argument must not contain a type variable; captured type variable " + typeVariable.getName() + " declared by " + typeVariable.getGenericDeclaration() + "\nSee " + TroubleshootingGuide.createUrl("typetoken-type-variable") What it means
Thrown by TypeToken's constructor (via verifyNoTypeVariable) when an anonymous TypeToken subclass captures a type variable, e.g. new TypeToken<List<T>>(){}. Because of type erasure the runtime type of a type variable is unknown to Gson, so allowing it would give a false sense of type-safety and risk a ClassCastException later. This is a deliberate fail-fast guard unless the system property gson.allowCapturingTypeVariables=true is set.
Source
Thrown at gson/src/main/java/com/google/gson/reflect/TypeToken.java:124
}
}
// 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) {
verifyNoTypeVariable(ownerType);
}
for (Type typeArgument : parameterizedType.getActualTypeArguments()) {
verifyNoTypeVariable(typeArgument);
}View on GitHub (pinned to 8b8628c656)
Solutions
- Replace the anonymous TypeToken with TypeToken.getParameterized(rawType, typeArguments) where the actual runtime Class/Type is passed in explicitly.
- Pass the Class<T> (or Type) of the element from the caller and build the token at runtime: TypeToken.getParameterized(List.class, elementType).
- If you understand the risk and truly need the old behavior, set system property gson.allowCapturingTypeVariables=true (not recommended for production).
- Obtain the Type from a reified source (e.g. a method parameter annotated/subclass) rather than capturing an unresolved type variable.
Example fix
// before
public <T> List<T> parseList(String json) {
TypeToken<List<T>> token = new TypeToken<List<T>>() {}; // throws
return gson.fromJson(json, token.getType());
}
// after
public <T> List<T> parseList(String json, Class<T> elementClass) {
Type listType = TypeToken.getParameterized(List.class, elementClass).getType();
return gson.fromJson(json, listType);
} Defensive patterns
Strategy: validation
Validate before calling
// Before constructing, ensure the element type is a concrete Class/Type, not a TypeVariable
import java.lang.reflect.TypeVariable;
boolean isSafeElementType(Type t) {
return !(t instanceof TypeVariable);
}
// usage
Type elementType = ...;
if (isSafeElementType(elementType)) {
TypeToken.getParameterized(List.class, elementType);
} else {
throw new IllegalArgumentException("Refuse to capture type variable in TypeToken");
} Type guard
static boolean isConcreteType(Type t) {
if (t instanceof Class) return true;
if (t instanceof ParameterizedType) {
ParameterizedType p = (ParameterizedType) t;
if (!isConcreteType(p.getRawType())) return false;
for (Type arg : p.getActualTypeArguments()) if (!isConcreteType(arg)) return false;
return true;
}
return !(t instanceof TypeVariable);
} Prevention
- Never capture unresolved type variables in anonymous TypeToken subclasses; pass a runtime Class/Type instead.
- Prefer TypeToken.getParameterized(rawClass, args) for runtime-driven generic types.
- Add a unit test that constructs the TypeToken for each generic helper to catch capture errors early.
- Avoid the gson.allowCapturingTypeVariables escape hatch unless you fully understand the ClassCastException risk.
When it happens
Trigger: Creating an anonymous TypeToken subclass inside a generic method or generic class where the type argument references the method/class type parameter, e.g. <T> void m() { new TypeToken<List<T>>(){}; }. The constructor resolves T as a TypeVariable, verifyNoTypeVariable detects it and throws IllegalArgumentException at TypeToken.java:124.
Common situations: Writing generic deserialization helpers such as <T> List<T> parseList(String json) and trying new TypeToken<List<T>>(){} to obtain the element type; refactoring a non-generic utility into a generic one without switching to TypeToken.getParameterized; generic repositories or DAO base classes.
Related errors
- Primitive type is not allowed
- Must specify owner type for {rawType}
- At most one lower bound is supported
- Exactly one upper bound must be specified
- When lower bound is specified, upper bound must be Object
AI-assisted analysis of google/gson@8b8628c656 (2026-08-04).
Data as JSON: /data/errors/771b1c8012c2f285.json.
Report an issue: GitHub.