{"record":{"id":"0fd0e8798892d999","repo":"google/gson","slug":"type-argument-does-not-satisfy-bounds-for-type","errorCode":null,"errorMessage":"Type argument {} does not satisfy bounds for type variable {} declared by {}","messagePattern":"Type argument (.+?) does not satisfy bounds for type variable (.+?) declared by (.+?)","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"gson/src/main/java/com/google/gson/reflect/TypeToken.java","lineNumber":432,"sourceCode":"    // Check for this here to avoid misleading exception thrown by ParameterizedTypeImpl\n    if (GsonTypes.requiresOwnerType(rawType)) {\n      throw new IllegalArgumentException(\n          \"Raw type \"\n              + rawClass.getName()\n              + \" is not supported because it requires specifying an owner type\");\n    }\n\n    for (int i = 0; i < expectedArgsCount; i++) {\n      Type typeArgument =\n          Objects.requireNonNull(typeArguments[i], \"Type argument must not be null\");\n      Class<?> rawTypeArgument = GsonTypes.getRawType(typeArgument);\n      TypeVariable<?> typeVariable = typeVariables[i];\n\n      for (Type bound : typeVariable.getBounds()) {\n        Class<?> rawBound = GsonTypes.getRawType(bound);\n\n        if (!rawBound.isAssignableFrom(rawTypeArgument)) {\n          throw new IllegalArgumentException(\n              \"Type argument \"\n                  + typeArgument\n                  + \" does not satisfy bounds for type variable \"\n                  + typeVariable\n                  + \" declared by \"\n                  + rawType);\n        }\n      }\n    }\n\n    return new TypeToken<>(GsonTypes.newParameterizedTypeWithOwner(null, rawClass, typeArguments));\n  }\n\n  /**\n   * Gets type literal for the array type whose elements are all instances of {@code componentType}.\n   */\n  public static TypeToken<?> getArray(Type componentType) {\n    return new TypeToken<>(GsonTypes.arrayOf(componentType));","sourceCodeStart":414,"sourceCodeEnd":450,"githubUrl":"https://github.com/google/gson/blob/310ac341f2f92a454b229bf21f70d2d18b2b6db7/gson/src/main/java/com/google/gson/reflect/TypeToken.java#L414-L450","documentation":"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>.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Pass a type argument whose raw class is assignable to the declared bound (e.g. Integer or Double for <T extends Number>).","Validate with boundClass.isAssignableFrom(argClass) before calling getParameterized.","If the bound is genuinely too restrictive, relax or redesign the generic declaration.","Surface a domain-specific error to the caller rather than letting the raw IllegalArgumentException escape."],"exampleFix":"// before\nclass NumberList<T extends Number> {}\nTypeToken<?> t = TypeToken.getParameterized(NumberList.class, String.class);\n\n// after\nclass NumberList<T extends Number> {}\nTypeToken<?> t = TypeToken.getParameterized(NumberList.class, Integer.class);","handlingStrategy":"validation","validationCode":"Class<?> raw = NumberList.class;\nTypeVariable<?>[] vars = raw.getTypeParameters();\nType[] args = { Integer.class };\nfor (int i = 0; i < args.length; i++) {\n  Class<?> argRaw = GsonTypes.getRawType(args[i]);\n  for (Type b : vars[i].getBounds()) {\n    if (!((Class<?>) GsonTypes.getRawType(b)).isAssignableFrom(argRaw)) {\n      throw new IllegalArgumentException(args[i] + \" violates bound \" + b);\n    }\n  }\n}\nTypeToken<?> t = TypeToken.getParameterized(raw, args);","typeGuard":"static boolean satisfiesBounds(Class<?> raw, int i, Type arg) {\n  Class<?> argRaw = GsonTypes.getRawType(arg);\n  for (Type b : raw.getTypeParameters()[i].getBounds()) {\n    if (!((Class<?>) GsonTypes.getRawType(b)).isAssignableFrom(argRaw)) return false;\n  }\n  return true;\n}","tryCatchPattern":"try {\n  return TypeToken.getParameterized(raw, args);\n} catch (IllegalArgumentException e) {\n  throw new DomainTypeException(\"Invalid generic binding for \" + raw, e);\n}","preventionTips":["Validate assignability to declared bounds before constructing the token.","Restrict user-supplied class inputs to an allowlist of permitted types.","Unit-test boundary cases for each bounded generic you expose."],"tags":["java","gson","generics","typetoken","bounds"],"backgroundTag":null,"analyzedSha":"310ac341f2f92a454b229bf21f70d2d18b2b6db7","analyzedAt":"2026-08-10T02:58:47.455Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}