W3docs

Java Generic Wildcards

Use upper-bounded, lower-bounded, and unbounded wildcards in Java generics, and the PECS rule.

Java Generic Wildcards

A wildcard is the ? token that appears in generic types in place of a concrete type argument — List<?>, List<? extends Number>, List<? super Integer>. It's the answer to a problem you hit almost immediately when you start writing generic code: List<Integer> is not a subtype of List<Number>, even though Integer is a subtype of Number. Wildcards are how you describe "a list of some Number" without committing to a specific element type — and they are, by some margin, the single most confusing piece of Java's type system.

The non-intuitive starting point

Here's the fact that makes wildcards necessary:

List<Integer> ints  = List.of(1, 2, 3);
List<Number>  nums  = ints;            // ❌ does not compile

Even though Integer extends Number, List<Integer> does not extend List<Number>. Generic types are invariantList<Sub> and List<Super> are unrelated, no matter what Sub and Super are.

The reason is sound, if surprising. If List<Integer> were a List<Number>, you could do this:

List<Number> nums = ints;     // pretend this is legal
nums.add(3.14);               // legal — 3.14 is a Number
int x = ints.get(3);          // KABOOM at runtime — it's a Double

The cast on the last line would explode. To keep that from happening, the compiler refuses the very first step: List<Integer> is not a List<Number>. End of story.

Wildcards are how you recover the flexibility safely.

The unbounded wildcard: List<?>

The simplest wildcard is the lonely ? — "a list of some unknown type":

public static void printAll(List<?> list) {
  for (Object o : list) System.out.println(o);
}

printAll(List.of(1, 2, 3));          // List<Integer> — OK
printAll(List.of("a", "b"));         // List<String>  — OK
printAll(new ArrayList<>());         // List<Object>  — OK

Inside the body, the only thing you can do with elements of a List<?> is read them as Object — because the compiler has no idea what ? is. You cannot add anything to a List<?> (with the sole exception of null):

public static void corrupt(List<?> list) {
  list.add("hello");   // ❌ does not compile — ? is unknown
  list.add(null);      // ✓ — null is a value of every reference type
}

List<?> is what you write when you want to express "I accept any list and I'm only going to read from it as Object."

Upper-bounded wildcard: ? extends T

When you need to read the elements as a specific type — say, treat them all as Number — use an upper-bounded wildcard:

public static double sum(List<? extends Number> list) {
  double total = 0;
  for (Number n : list) total += n.doubleValue();   // legal — every element IS-A Number
  return total;
}

sum(List.of(1, 2, 3));          // List<Integer> — OK, Integer extends Number
sum(List.of(1.5, 2.5));         // List<Double>  — OK
sum(List.of(1L, 2L, 3L));       // List<Long>    — OK

List<? extends Number> reads as "a list of some specific type that is Number or a subtype of Number." You can read from it as Number. You cannot add anything to it, again with the null exception — because the compiler doesn't know which Number subtype the list actually holds. Adding an Integer to a List<? extends Number> that's secretly a List<Double> would corrupt it; rather than try to figure out which subtype it is, the compiler simply refuses every add.

Lower-bounded wildcard: ? super T

The mirror image. ? super T means "the list's element type is T or some supertype of T":

public static void addOneTwoThree(List<? super Integer> list) {
  list.add(1);
  list.add(2);
  list.add(3);
}

List<Integer> ints   = new ArrayList<>();   addOneTwoThree(ints);   // ✓
List<Number>  nums   = new ArrayList<>();   addOneTwoThree(nums);   // ✓ — Number is a supertype of Integer
List<Object>  objs   = new ArrayList<>();   addOneTwoThree(objs);   // ✓ — Object is too

Here you can add any Integer (or subtype) safely — the list's element type is guaranteed to be Integer or some ancestor of it, so an Integer fits. What you can't do is read a specific type out — the most you can say about an element is that it's an Object, because the actual list might be List<Object>.

The PECS rule

There is one mnemonic every Java developer eventually memorises:

PECS — Producer Extends, Consumer Super.

It's the rule of thumb for when to use which wildcard:

  • If the parameter produces values (you read from it): use ? extends T.
  • If the parameter consumes values (you write to it): use ? super T.

The canonical signature it produces is Collections.copy:

public static <T> void copy(List<? super T> dest, List<? extends T> src) {
  for (int i = 0; i < src.size(); i++) {
    dest.set(i, src.get(i));
  }
}

src is read from (it produces Ts) — ? extends T. dest is written into (it consumes Ts) — ? super T. That's the whole reason for the asymmetry: the same signature works whether src is a List<Integer> and dest is a List<Number>, or the other way round, as long as T is a meeting point between them.

If you remember nothing else from this chapter, remember PECS.

When not to use a wildcard

If a parameter is both read from and written into in the same method, neither ? extends T nor ? super T works — neither lets you do both. In that case, just use a normal type parameter:

public static <T> void swap(List<T> list, int i, int j) {
  T tmp = list.get(i);              // read
  list.set(i, list.get(j));         // write
  list.set(j, tmp);                 // write
}

A wildcard is the right tool when one side of the relationship is "I only read" or "I only write." A type parameter is the right tool when you need to talk about a specific element type on both sides.

Wildcards vs. bounded type parameters

Compare:

public static <T extends Number> double sumNamed(List<T> list)         { ... }
public static            double sumWildcard(List<? extends Number> list) { ... }

Functionally these accept the same set of arguments. The difference is what the body can say:

  • The named form (<T extends Number>) gives you a name T — useful if you want to return T, accept another List<T> as a second parameter, or write T tmp = list.get(0) to preserve the precise element type.
  • The wildcard form (? extends Number) gives you no name — you can only refer to elements as Number. It's tighter in the API (no name leaks into the signature) but less expressive in the body.

Rule of thumb: if you only need the elements as Number, the wildcard is the smaller, cleaner choice. If the body needs to talk about a specific T, name it.

A worked example: PECS in practice

The program copies elements from one list into another and computes the running sum — both operations parameterised the PECS way. Watch the call sites: copyOf(intList, numberList) mixes element types because the wildcards permit a Number destination to accept Integer values.

java— editable, runs on the server

sum accepts a List<Integer> and a List<Double> because the wildcard says "some Number subtype." fillWithSquares adds Integer values into a List<Number> because the wildcard says "any list that can hold Integer or one of its ancestors." copyTo uses both — the source is a producer, the destination is a consumer, and T is the shared element type the compiler infers from the two sides agreeing.

What's next

You've seen the four ways generics show up in source code — classes, methods, interfaces, and wildcards. Now we look one layer down at how the JVM actually implements all this. The answer — type erasure — explains some surprising restrictions (no new T(), no instanceof T, no generic arrays) and is the single piece of insight that makes Java's generic story click. Continue to Java Type Erasure.

Practice

Practice

You're writing `addAll(Collection<? extends T> src, Collection<? super T> dest)`. Why this exact combination of wildcards?