List<? extends Number> and List<? super Number> look nearly identical and mean almost opposite things, which is why wildcard generics have a reputation for being the part of Java's type system developers memorize a rule for rather than truly reason about. The rule — PECS, "Producer Extends, Consumer Super" — is genuinely mechanical once the underlying problem is clear, and it's worth understanding the problem before the mnemonic.

Why List Isn't a List

The instinct that List<Integer> should be usable wherever List<Number> is expected feels obviously safe — every Integer is a Number. But Java generics are deliberately invariant by default: List<Integer> is not a subtype of List<Number>, and the reason is mutation.

java
List<Integer> ints = new ArrayList<>();
List<Number> numbers = ints; // if this compiled...
numbers.add(3.14);           // ...this would silently corrupt a List<Integer>

If that assignment were legal, the second line would insert a Double into what's actually backed by a List<Integer>, and the corruption wouldn't surface until something later tries to read an Integer back out and gets a ClassCastException at a completely unrelated line of code. Invariance closes this hole entirely, at the cost of flexibility that wildcards exist to restore, safely, in the specific cases where it's actually safe.

Producer Extends: Reading Safely

? extends T means "some unknown subtype of T" — you can safely read a T out of it (every element genuinely is at least a T), but you cannot safely add to it, because the compiler doesn't know the concrete type and any object you'd add might not match it.

java
static double sum(List<? extends Number> numbers) {
    double total = 0;
    for (Number n : numbers) {  // reading is safe — every element is at least a Number
        total += n.doubleValue();
    }
    return total;
    // numbers.add(5); would not compile — the list might actually be a List<Integer>
}

sum(List.of(1, 2, 3));       // List<Integer> accepted
sum(List.of(1.5, 2.5));      // List<Double> accepted too

Consumer Super: Writing Safely

? super T means "some unknown supertype of T" — the reverse trade-off. You can safely add a T (any supertype's list can legally hold a T), but reading gives you only Object, since the compiler doesn't know how far up the hierarchy the actual type sits.

java
static void addNumbers(List<? super Integer> list) {
    list.add(1);      // safe — an Integer fits in any supertype-of-Integer list
    list.add(2);
    // Number n = list.get(0); would not compile — could only be read as Object
}

List<Number> numbers = new ArrayList<>();
addNumbers(numbers);   // List<Number> accepted, since Number is a supertype of Integer

PECS as a Mechanical Rule

You're writing a method that... Use Mnemonic
Only reads elements out of the collection ? extends T Producer Extends
Only writes elements into the collection ? super T Consumer Super
Both reads and writes Exact type T, no wildcard Neither applies

Collections.copy is the textbook example that needs both roles at once, and its signature spells out PECS directly:

java
public static <T> void copy(List<? super T> dest, List<? extends T> src)

src only ever gets read from — it's a producer of T, so extends. dest only ever gets written to — it's a consumer of T, so super. Once you're naming parameters by the role they play (does this argument hand data out, or take data in?) rather than trying to reason about subtyping directly, PECS stops being a mnemonic you look up and becomes the obvious shape of the method signature.