google/gson · error · IllegalArgumentException

Exactly one upper bound must be specified

Error message

Exactly one upper bound must be specified

What it means

Thrown by WildcardTypeImpl (GsonTypes) when the number of upper bounds is not exactly one. A Java wildcard always has an implicit upper bound (Object when unspecified), so Gson normalizes every wildcard to exactly one upper bound. Zero upper bounds or more than one upper bound (other than the single-bound case) is rejected because the target Java version supports at most one bound per wildcard.

Source

Thrown at gson/src/main/java/com/google/gson/internal/GsonTypes.java:630

  }

  /**
   * The WildcardType interface supports multiple upper bounds and multiple lower bounds. We only
   * support what the target Java version supports - at most one bound, see also
   * https://bugs.openjdk.java.net/browse/JDK-8250660. If a lower bound is set, the upper bound must
   * be Object.class.
   */
  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]);
      }

View on GitHub (pinned to 8b8628c656)

Solutions

  1. Ensure the WildcardType reports exactly one upper bound; if multiple intersection bounds exist, collapse to their common supertype or use Object.class.
  2. Use Types.subtypeOf(bound) so Gson sets upperBound = bound and handles normalization for you.
  3. Avoid constructing wildcard types manually; let the compiler/Gson derive them.

Example fix

// before: zero upper bounds
public Type[] getUpperBounds() { return new Type[0]; }

// after: exactly one
public Type[] getUpperBounds() { return new Type[] { Number.class }; }
Defensive patterns

Strategy: validation

Validate before calling

static Type normalizeUpperBounds(WildcardType w) {
  Type[] ub = w.getUpperBounds();
  if (ub.length != 1) {
    Type bound = ub.length == 0 ? Object.class : ub[0];
    return com.google.gson.internal.$Gson$Types.subtypeOf(bound);
  }
  return w;
}

Type guard

static boolean hasExactlyOneUpperBound(WildcardType w) {
  return w.getUpperBounds().length == 1;
}

Try / catch

try {
  canonicalize(wildcardType);
} catch (IllegalArgumentException e) {
  if (e.getMessage().contains("upper bound")) {
    wildcardType = normalizeUpperBounds(wildcardType);
  } else throw e;
}

Prevention

When it happens

Trigger: Constructing a wildcard type whose getUpperBounds() returns an empty array or an array with length >= 2. Reached via internal canonicalization of a hand-built or reflectively-obtained WildcardType that does not expose exactly one upper bound.

Common situations: Custom WildcardType implementations returning no upper bounds or multiple intersection upper bounds; reflective types produced by annotation processors modeling intersection types; misuse of Types helper methods.

Related errors


AI-assisted analysis of google/gson@8b8628c656 (2026-08-04). Data as JSON: /data/errors/59048385c6e20ac5.json. Report an issue: GitHub.