{"record":{"id":"196f79be63689f00","repo":"google/gson","slug":"must-only-create-direct-subclasses-of-typetoken","errorCode":null,"errorMessage":"Must only create direct subclasses of TypeToken","messagePattern":"Must only create direct subclasses of TypeToken","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"gson/src/main/java/com/google/gson/reflect/TypeToken.java","lineNumber":118,"sourceCode":"        Type typeArgument = GsonTypes.canonicalize(parameterized.getActualTypeArguments()[0]);\n\n        if (isCapturingTypeVariablesForbidden()) {\n          verifyNoTypeVariable(typeArgument);\n        }\n        return typeArgument;\n      }\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) {","sourceCodeStart":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/google/gson/blob/310ac341f2f92a454b229bf21f70d2d18b2b6db7/gson/src/main/java/com/google/gson/reflect/TypeToken.java#L100-L136","documentation":"Thrown by TypeToken.getTypeTokenTypeArgument when the anonymous subclass's generic superclass is a ParameterizedType but its rawType is NOT TypeToken itself - meaning the user created a subclass of a subclass of TypeToken (e.g. `class MyToken extends TypeToken<List<String>>{}` then `new MyToken(){}`). TypeToken only supports direct subclasses.","triggerScenarios":"Defining `class StringListToken extends TypeToken<List<String>>` and then instantiating it (directly or anonymously) for use with Gson. The constructor's check requires the immediate generic superclass to be TypeToken; any intermediate type breaks this invariant.","commonSituations":"Refactoring repeated TypeToken usages into a named subclass for reuse; library authors trying to provide pre-baked TypeTokens; framework code that wraps TypeToken; well-intentioned DRY that violates TypeToken's contract.","solutions":["Do not subclass TypeToken transitively; instantiate it directly as an anonymous class: `new TypeToken<List<String>>() {}`.","If you need a reusable handle, hold a TypeToken<?> field initialized with the anonymous form rather than a named subclass.","For runtime-determined types, use TypeToken.getParameterized(...) or TypeToken.get(Class) instead of a named subclass."],"exampleFix":"// before (broken)\nclass StringListToken extends TypeToken<List<String>> {}\nTypeToken<List<String>> t = new StringListToken(); // throws\n\n// after (direct anonymous subclass)\nTypeToken<List<String>> t = new TypeToken<List<String>>() {};\n\n// after (reusable constant)\npublic final class Tokens {\n  public static final TypeToken<List<String>> STRING_LIST = new TypeToken<List<String>>() {};\n}","handlingStrategy":"validation","validationCode":"// enforce direct-subclass invariant at build/test time\nClass<?> c = token.getClass();\nClass<?> sup = c.getSuperclass();\nif (sup != TypeToken.class) {\n  throw new IllegalStateException(\"TypeToken subclass chain too deep: \" + sup);\n}","typeGuard":"public static boolean isDirectTypeTokenSubclass(Class<?> c) {\n  return c.getSuperclass() == TypeToken.class;\n}","tryCatchPattern":"try {\n  TypeToken<List<String>> t = new StringListToken(); // subclass chain\n  return gson.fromJson(json, t.getType());\n} catch (IllegalStateException e) {\n  if (e.getMessage().equals(\"Must only create direct subclasses of TypeToken\")) {\n    // refactor to direct anonymous subclass\n    TypeToken<List<String>> t2 = new TypeToken<List<String>>() {};\n    return gson.fromJson(json, t2.getType());\n  }\n  throw e;\n}","preventionTips":["Never create named subclasses of TypeToken; always use anonymous direct subclasses.","Hold reusable TypeTokens as static final fields of direct anonymous instances.","Use TypeToken.getParameterized when types are runtime-known.","Code-review TypeToken usages to catch indirect subclassing early."],"tags":["gson","type-token","generics","subclassing","illegal-state"],"backgroundTag":null,"analyzedSha":"310ac341f2f92a454b229bf21f70d2d18b2b6db7","analyzedAt":"2026-08-10T02:58:47.455Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}