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

  1. Replace the anonymous TypeToken with TypeToken.getParameterized(rawType, typeArguments) where the actual runtime Class/Type is passed in explicitly.
  2. Pass the Class<T> (or Type) of the element from the caller and build the token at runtime: TypeToken.getParameterized(List.class, elementType).
  3. If you understand the risk and truly need the old behavior, set system property gson.allowCapturingTypeVariables=true (not recommended for production).
  4. 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

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


AI-assisted analysis of google/gson@8b8628c656 (2026-08-04). Data as JSON: /data/errors/771b1c8012c2f285.json. Report an issue: GitHub.