google/gson · error · IllegalArgumentException
When lower bound is specified, upper bound must be Object
Error message
When lower bound is specified, upper bound must be Object
What it means
Thrown by WildcardTypeImpl when a lower bound is supplied but the upper bound is not exactly Object.class. A wildcard is either upper-bounded (<? extends T>, upper != Object, lower == none) or lower-bounded (<? super T>, upper == Object, lower == T); mixing both is meaningless in Java source. Gson enforces the invariant at GsonTypes.java:636.
Solutions
- Decide which form you mean: upper-bounded (only upperBounds set, lower empty) or lower-bounded (upper = Object.class, lower set).
- Drop the lower bound if you really want an upper-bounded wildcard, or set upper to Object.class if you really want a lower-bounded wildcard.
- Do not synthesize bounds from arbitrary type metadata without normalizing to one of the two legal wildcard shapes.
Example fix
// before (ambiguous; throws)
new WildcardTypeImpl(new Type[]{Number.class}, new Type[]{String.class});
// after (lower-bounded)
new WildcardTypeImpl(new Type[]{Object.class}, new Type[]{String.class}); Defensive patterns
Strategy: validation
Validate before calling
boolean validWildcard(Type[] up, Type[] lo) {
if (lo != null && lo.length == 1) {
return up != null && up.length == 1 && up[0] == Object.class;
}
return true;
} Type guard
static boolean isLegalWildcard(Type[] up, Type[] lo) {
return (lo == null || lo.length == 0) || (up != null && up.length == 1 && up[0] == Object.class);
} Prevention
- Remember: a wildcard is either upper-bounded or lower-bounded, never both meaningfully.
- Reject type metadata containing both bounds at the bridge boundary rather than forwarding it.
When it happens
Trigger: Constructing a WildcardTypeImpl with both a non-Object upper bound and a lower bound, e.g. new WildcardTypeImpl(new Type[]{Number.class}, new Type[]{String.class}).
Common situations: Programmatic Type builders that fill both arrays from a generic descriptor; bridging code that mirrors annotation/type-use metadata containing both bounds; malformed test fixtures.
Related errors
- At most one lower bound is supported
- Exactly one upper bound must be specified
- Must specify owner type for
- Primitive type is not allowed
- Expected a Class, ParameterizedType, or GenericArrayType…
AI-assisted analysis of google/gson@310ac341f2 (2026-08-10).
Data as JSON: /api/errors/a85063cdfa813b6e.
Report an issue: GitHub.
Appendix: source
Thrown at gson/src/main/java/com/google/gson/internal/GsonTypes.java:637
*/
private static final class WildcardTypeImpl implements WildcardType {
private final Type upperBound;
private final Type lowerBound;
WildcardTypeImpl(Type[] upperBounds, Type[] lowerBounds) {
if (lowerBounds.length > 1) {
throw new IllegalArgumentException("At most one lower bound is supported");
}
if (upperBounds.length != 1) {
throw new IllegalArgumentException("Exactly one upper bound must be specified");
}
if (lowerBounds.length == 1) {
requireNonNull(lowerBounds[0]);
checkNotPrimitive(lowerBounds[0]);
if (upperBounds[0] != Object.class) {
throw new IllegalArgumentException(
"When lower bound is specified, upper bound must be Object");
}
this.lowerBound = canonicalize(lowerBounds[0]);
this.upperBound = Object.class;
} else {
requireNonNull(upperBounds[0]);
checkNotPrimitive(upperBounds[0]);
this.lowerBound = null;
this.upperBound = canonicalize(upperBounds[0]);
}
}
@Override
public Type[] getUpperBounds() {
return new Type[] {upperBound};
}
View on GitHub (pinned to 310ac341f2)