google/gson · error · IllegalArgumentException

Type argument {} does not satisfy bounds for type variable {

Error message

Type argument {} does not satisfy bounds for type variable {} declared by {}

What it means

Thrown by TypeToken.getParameterized during the per-argument bounds loop (TypeToken.java:422-441). For each type argument it checks every declared bound of the corresponding type variable via rawBound.isAssignableFrom(rawTypeArgument); if the argument is not assignable to the bound, the type is rejected. This prevents constructing a generic type that the Java compiler itself would refuse, e.g. List<String> for a class declared <T extends Number>.

Source

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

    // Check for this here to avoid misleading exception thrown by ParameterizedTypeImpl
    if (GsonTypes.requiresOwnerType(rawType)) {
      throw new IllegalArgumentException(
          "Raw type "
              + rawClass.getName()
              + " is not supported because it requires specifying an owner type");
    }

    for (int i = 0; i < expectedArgsCount; i++) {
      Type typeArgument =
          Objects.requireNonNull(typeArguments[i], "Type argument must not be null");
      Class<?> rawTypeArgument = GsonTypes.getRawType(typeArgument);
      TypeVariable<?> typeVariable = typeVariables[i];

      for (Type bound : typeVariable.getBounds()) {
        Class<?> rawBound = GsonTypes.getRawType(bound);

        if (!rawBound.isAssignableFrom(rawTypeArgument)) {
          throw new IllegalArgumentException(
              "Type argument "
                  + typeArgument
                  + " does not satisfy bounds for type variable "
                  + typeVariable
                  + " declared by "
                  + rawType);
        }
      }
    }

    return new TypeToken<>(GsonTypes.newParameterizedTypeWithOwner(null, rawClass, typeArguments));
  }

  /**
   * Gets type literal for the array type whose elements are all instances of {@code componentType}.
   */
  public static TypeToken<?> getArray(Type componentType) {
    return new TypeToken<>(GsonTypes.arrayOf(componentType));

View on GitHub (pinned to 310ac341f2)

Solutions

  1. Pass a type argument whose raw class is assignable to the declared bound (e.g. Integer or Double for <T extends Number>).
  2. Validate with boundClass.isAssignableFrom(argClass) before calling getParameterized.
  3. If the bound is genuinely too restrictive, relax or redesign the generic declaration.
  4. Surface a domain-specific error to the caller rather than letting the raw IllegalArgumentException escape.

Example fix

// before
class NumberList<T extends Number> {}
TypeToken<?> t = TypeToken.getParameterized(NumberList.class, String.class);

// after
class NumberList<T extends Number> {}
TypeToken<?> t = TypeToken.getParameterized(NumberList.class, Integer.class);
Defensive patterns

Strategy: validation

Validate before calling

Class<?> raw = NumberList.class;
TypeVariable<?>[] vars = raw.getTypeParameters();
Type[] args = { Integer.class };
for (int i = 0; i < args.length; i++) {
  Class<?> argRaw = GsonTypes.getRawType(args[i]);
  for (Type b : vars[i].getBounds()) {
    if (!((Class<?>) GsonTypes.getRawType(b)).isAssignableFrom(argRaw)) {
      throw new IllegalArgumentException(args[i] + " violates bound " + b);
    }
  }
}
TypeToken<?> t = TypeToken.getParameterized(raw, args);

Type guard

static boolean satisfiesBounds(Class<?> raw, int i, Type arg) {
  Class<?> argRaw = GsonTypes.getRawType(arg);
  for (Type b : raw.getTypeParameters()[i].getBounds()) {
    if (!((Class<?>) GsonTypes.getRawType(b)).isAssignableFrom(argRaw)) return false;
  }
  return true;
}

Try / catch

try {
  return TypeToken.getParameterized(raw, args);
} catch (IllegalArgumentException e) {
  throw new DomainTypeException("Invalid generic binding for " + raw, e);
}

Prevention

When it happens

Trigger: Passing String as the type argument for a variable bounded as <T extends Number>, e.g. getParameterized(NumberList.class, String.class) where NumberList<T extends Number>. Also triggered by bounded wildcards or custom interfaces where the argument class does not implement the required bound.

Common situations: Building TypeTokens dynamically from user-supplied class names without validating the bound; generic repository/helper APIs that accept a Class<?> and pass it straight to getParameterized; migrating from raw types to bounded generics and forgetting the constraint.

Related errors


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