google/gson · error · IllegalStateException

Must only create direct subclasses of TypeToken

Error message

Must only create direct subclasses of TypeToken

What it means

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.

Solutions

  1. Do not subclass TypeToken transitively; instantiate it directly as an anonymous class: `new TypeToken<List<String>>() {}`.
  2. If you need a reusable handle, hold a TypeToken<?> field initialized with the anonymous form rather than a named subclass.
  3. For runtime-determined types, use TypeToken.getParameterized(...) or TypeToken.get(Class) instead of a named subclass.

Example fix

// before (broken)
class StringListToken extends TypeToken<List<String>> {}
TypeToken<List<String>> t = new StringListToken(); // throws

// after (direct anonymous subclass)
TypeToken<List<String>> t = new TypeToken<List<String>>() {};

// after (reusable constant)
public final class Tokens {
  public static final TypeToken<List<String>> STRING_LIST = new TypeToken<List<String>>() {};
}
Defensive patterns

Strategy: validation

Validate before calling

// enforce direct-subclass invariant at build/test time
Class<?> c = token.getClass();
Class<?> sup = c.getSuperclass();
if (sup != TypeToken.class) {
  throw new IllegalStateException("TypeToken subclass chain too deep: " + sup);
}

Type guard

public static boolean isDirectTypeTokenSubclass(Class<?> c) {
  return c.getSuperclass() == TypeToken.class;
}

Try / catch

try {
  TypeToken<List<String>> t = new StringListToken(); // subclass chain
  return gson.fromJson(json, t.getType());
} catch (IllegalStateException e) {
  if (e.getMessage().equals("Must only create direct subclasses of TypeToken")) {
    // refactor to direct anonymous subclass
    TypeToken<List<String>> t2 = new TypeToken<List<String>>() {};
    return gson.fromJson(json, t2.getType());
  }
  throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of google/gson@310ac341f2 (2026-08-10). Data as JSON: /api/errors/196f79be63689f00. Report an issue: GitHub.

Appendix: source

Thrown at gson/src/main/java/com/google/gson/reflect/TypeToken.java:118

        Type typeArgument = GsonTypes.canonicalize(parameterized.getActualTypeArguments()[0]);

        if (isCapturingTypeVariablesForbidden()) {
          verifyNoTypeVariable(typeArgument);
        }
        return typeArgument;
      }
    }
    // 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) {

View on GitHub (pinned to 310ac341f2)