google/gson · error · IllegalArgumentException
TypeToken type argument must not contain a type variable…
Error message
TypeToken type argument must not contain a type variable; captured type variable {} declared by {}
See {} What it means
Thrown by TypeToken.verifyNoTypeVariable when the captured type argument contains a TypeVariable (e.g. T). Because of erasure, Gson has no runtime type for a type variable, so capturing one (like `new TypeToken<List<T>>(){}` inside a generic method) would silently degrade to a raw type and cause ClassCastException later. The check is skipped only if system property gson.allowCapturingTypeVariables=true.
Solutions
- Pass the Class<T> (or Type) explicitly and construct the TypeToken at runtime via TypeToken.getParameterized(List.class, elementClass).
- Accept that the type argument must be statically known; restructure so the caller supplies a concrete TypeToken.
- Only as a last resort, set -Dgson.allowCapturingTypeVariables=true to disable the check (you accept the runtime ClassCastException risk and loss of type-safety).
Example fix
// before (broken)
public <T> TypeToken<List<T>> listToken() {
return new TypeToken<List<T>>() {}; // captures T -> throws
}
// after: pass Class<T> and build at runtime
public <T> TypeToken<List<T>> listToken(Class<T> elementType) {
return (TypeToken<List<T>>) (TypeToken<?>) TypeToken.getParameterized(List.class, elementType);
}
// usage:
TypeToken<List<String>> t = listToken(String.class); Defensive patterns
Strategy: validation
Validate before calling
// prevent capture at compile time: refuse generic methods returning TypeToken<T>
// prefer passing Class<T> explicitly:
public <T> TypeToken<List<T>> listOf(Class<T> cls) {
return (TypeToken<List<T>>) (TypeToken<?>) TypeToken.getParameterized(List.class, cls);
} Type guard
public static <T> boolean capturesTypeVariable(TypeToken<T> t) {
// best-effort: type variables appear in toString as their name without a package
String s = t.getType().toString();
return s.matches(".*\\b[T-Z]\\b.*") && !(t.getType() instanceof Class);
} Try / catch
try {
return new TypeToken<List<T>>() {}; // inside generic method
} catch (IllegalArgumentException e) {
if (e.getMessage().startsWith("TypeToken type argument must not contain a type variable")) {
// fall back to runtime construction with the actual Class
return (TypeToken<List<T>>) (TypeToken<?>) TypeToken.getParameterized(List.class, elementType);
}
throw e;
} Prevention
- Never write `new TypeToken<...T...>(){}` where T is a generic parameter.
- Pass Class<T>/Type explicitly and build with TypeToken.getParameterized.
- Static-analysis: warn on any TypeToken instantiation inside a generic method/class.
- Avoid -Dgson.allowCapturingTypeVariables=true in production; it just defers the failure to a ClassCastException.
When it happens
Trigger: Inside a generic method/class, writing `new TypeToken<List<T>>(){}` (or `new TypeToken<T>(){}`) where T is a type variable of the enclosing generic scope. The anonymous subclass captures T, which verifyNoTypeVariable rejects recursively across arrays, parameterized types, wildcards, and owner types.
Common situations: Writing generic helper methods like `<T> TypeToken<List<T>> token()` that try to capture T; builders/factories parameterized by T that build TypeTokens; refactoring that accidentally widens a type variable into a TypeToken; migrating from raw types to generics and forgetting T cannot be captured.
Related errors
- rawType must be of type Class, but was
- TypeToken captured `null` as type argument; probably a…
- Must only create direct subclasses of TypeToken
- Must specify owner type for
- Primitive type is not allowed
AI-assisted analysis of google/gson@310ac341f2 (2026-08-10).
Data as JSON: /api/errors/b96fc7a33b49e651.
Report an issue: GitHub.
Appendix: 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 310ac341f2)