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

  1. Pass the Class<T> (or Type) explicitly and construct the TypeToken at runtime via TypeToken.getParameterized(List.class, elementClass).
  2. Accept that the type argument must be statically known; restructure so the caller supplies a concrete TypeToken.
  3. 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

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


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)