{"id":"771b1c8012c2f285","repo":"google/gson","slug":"typetoken-type-argument-must-not-contain-a-type-va","errorCode":null,"errorMessage":"TypeToken type argument must not contain a type variable; captured type variable \" + typeVariable.getName() + \" declared by \" + typeVariable.getGenericDeclaration() + \"\\nSee \" + TroubleshootingGuide.createUrl(\"typetoken-type-variable\")","messagePattern":"TypeToken type argument must not contain a type variable; captured type variable \" \\+ typeVariable\\.getName\\(\\) \\+ \" declared by \" \\+ typeVariable\\.getGenericDeclaration\\(\\) \\+ \"\\\\nSee \" \\+ TroubleshootingGuide\\.createUrl\\(\"typetoken-type-variable\"\\)","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"gson/src/main/java/com/google/gson/reflect/TypeToken.java","lineNumber":124,"sourceCode":"      }\n    }\n    // Check for raw TypeToken as superclass\n    else if (superclass == TypeToken.class) {\n      throw new IllegalStateException(\n          \"TypeToken must be created with a type argument: new TypeToken<...>() {}; When using code\"\n              + \" shrinkers (ProGuard, R8, ...) make sure that generic signatures are preserved.\"\n              + \"\\nSee \"\n              + TroubleshootingGuide.createUrl(\"type-token-raw\"));\n    }\n\n    // User created subclass of subclass of TypeToken\n    throw new IllegalStateException(\"Must only create direct subclasses of TypeToken\");\n  }\n\n  private static void verifyNoTypeVariable(Type type) {\n    if (type instanceof TypeVariable) {\n      TypeVariable<?> typeVariable = (TypeVariable<?>) type;\n      throw new IllegalArgumentException(\n          \"TypeToken type argument must not contain a type variable; captured type variable \"\n              + typeVariable.getName()\n              + \" declared by \"\n              + typeVariable.getGenericDeclaration()\n              + \"\\nSee \"\n              + TroubleshootingGuide.createUrl(\"typetoken-type-variable\"));\n    } else if (type instanceof GenericArrayType) {\n      verifyNoTypeVariable(((GenericArrayType) type).getGenericComponentType());\n    } else if (type instanceof ParameterizedType) {\n      ParameterizedType parameterizedType = (ParameterizedType) type;\n      Type ownerType = parameterizedType.getOwnerType();\n      if (ownerType != null) {\n        verifyNoTypeVariable(ownerType);\n      }\n\n      for (Type typeArgument : parameterizedType.getActualTypeArguments()) {\n        verifyNoTypeVariable(typeArgument);\n      }","sourceCodeStart":106,"sourceCodeEnd":142,"githubUrl":"https://github.com/google/gson/blob/8b8628c65699bc4421696183c62ae0c1b9b281dc/gson/src/main/java/com/google/gson/reflect/TypeToken.java#L106-L142","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\npublic <T> List<T> parseList(String json) {\n  TypeToken<List<T>> token = new TypeToken<List<T>>() {}; // throws\n  return gson.fromJson(json, token.getType());\n}\n\n// after\npublic <T> List<T> parseList(String json, Class<T> elementClass) {\n  Type listType = TypeToken.getParameterized(List.class, elementClass).getType();\n  return gson.fromJson(json, listType);\n}","handlingStrategy":"validation","validationCode":"// Before constructing, ensure the element type is a concrete Class/Type, not a TypeVariable\nimport java.lang.reflect.TypeVariable;\n\nboolean isSafeElementType(Type t) {\n  return !(t instanceof TypeVariable);\n}\n\n// usage\nType elementType = ...;\nif (isSafeElementType(elementType)) {\n  TypeToken.getParameterized(List.class, elementType);\n} else {\n  throw new IllegalArgumentException(\"Refuse to capture type variable in TypeToken\");\n}","typeGuard":"static boolean isConcreteType(Type t) {\n  if (t instanceof Class) return true;\n  if (t instanceof ParameterizedType) {\n    ParameterizedType p = (ParameterizedType) t;\n    if (!isConcreteType(p.getRawType())) return false;\n    for (Type arg : p.getActualTypeArguments()) if (!isConcreteType(arg)) return false;\n    return true;\n  }\n  return !(t instanceof TypeVariable);\n}","tryCatchPattern":null,"preventionTips":["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."],"tags":["gson","typetoken","generics","reflection","type-erasure"],"analyzedSha":"8b8628c65699bc4421696183c62ae0c1b9b281dc","analyzedAt":"2026-08-04T19:12:22.202Z","schemaVersion":2}