Java Generic Super Bound: Using ? super T
Learn how the `? super T` lower bound wildcard works in Java generics, when to use it, and how the PECS rule guides producer and consumer wildcard choices.
The super bound in Java generics is a lower bound wildcard. When you write List<? super Integer>, the list's element type can be Integer or any supertype of Integer, such as Number or Object. This is the opposite of the upper bound wildcard ? extends Integer, which restricts the element type to Integer or its subtypes. The ? super T syntax appears in method signatures, variable declarations, and generic type arguments.
What ? super T Declares in Java Generics
The lower bound wildcard tells the compiler that the actual type argument is T or one of its supertypes. Consider this method declaration:
public static void addNumbers(List<? super Integer> numbers) { numbers.add(42); }
The method accepts any List whose element type is Integer or a supertype of Integer, such as List<Integer>, List<Number>, or List<Object>. The compiler knows that whatever the actual type argument is, it is a supertype of Integer, so adding an Integer is always safe. The bound is inclusive: List<Integer> itself satisfies ? super Integer.
Reading and Writing Through a Super Bound
The critical difference between ? super T and ? extends T is what operations the compiler allows. With a lower bound, you can safely write values of type T into the collection, but you cannot read them back as T.
public static void fill(List<? super Integer> list) { list.add(1); list.add(2); // Integer value = list.get(0); // does not compile }
When you read from List<? super Integer>, the only static type guaranteed is Object, because the actual element type could be Number or Object. To treat the result as an Integer, you would need an explicit cast, and that cast might fail at runtime; the compiler will not insert it implicitly.
This asymmetry is the foundation of the PECS rule: a ? super T collection is a consumer of T values, while a ? extends T collection is a producer of T values.
The PECS Rule: Producer Extends, Consumer Super
PECS stands for "Producer Extends, Consumer Super." If a method parameter produces values of type T, declare it with ? extends T. If it consumes values of type T, declare it with ? super T.
Consider a generic copy method:
public static <T> void copy(List<? extends T> source, List<? super T> target) { for (T item : source) { target.add(item); } }
The source produces T values, so it uses ? extends T. The target consumes T values, so it uses ? super T. This signature lets you copy from List<Integer> to List<Number>:
List<Integer> integers = List.of(1, 2, 3); List<Number> numbers = new ArrayList<>(); copy(integers, numbers);
The lower-bound wildcard on the target is what makes the destination flexible: it accepts a list of T or any supertype of T. A concrete List<T> destination would not accept a wildcard destination such as List<? super T>. The standard Collections.copy method follows the same pattern, declaring its destination as List<? super T> and its source as List<? extends T>.
Type Erasure and Runtime Behavior
Java generics are erased at compile time. The ? super bound exists only in the source code and the compiled signature; at runtime, a List<? super Integer> is just an ArrayList or whatever the concrete type is. No additional runtime checks are performed for the bound itself.
The practical consequence is that lower bounds do not add runtime overhead. The compiler uses the bound to validate calls and insert casts where needed, but the generated bytecode is effectively the same as for the corresponding raw type or unbounded wildcard. This means you can use ? super freely in API design without worrying about performance costs. The cost is purely at compile time, in the form of stricter type checking that prevents invalid writes.
Common Mistakes with Lower Bounds
One frequent error is trying to read a specific type from a ? super collection. Another is declaring a variable with ? super and then attempting to add a value that is not guaranteed to fit.
List<? super Integer> list = new ArrayList<Number>(); // list.add("text"); // does not compile
Adding a String fails because String is not a subtype of Integer; if the actual collection were List<Integer>, adding a String would be invalid. The compiler only permits adding Integer or null, because Integer is the lower bound. Any other type could be incompatible with the actual element type.
Another mistake is using ? super when the method only reads from the collection. If a method never writes, ? extends or an unbounded wildcard is more flexible and communicates the intent more clearly. A lower bound on a read-only parameter forces the caller to pass a collection of T or a supertype, which is unnecessarily restrictive.
Choosing Between ? extends, ? super, and Unbounded Wildcards
The choice depends on what the method does with the collection:
| Wildcard | Permitted operations | Typical use |
|---|---|---|
? extends T | Read as T | Producer of values |
? super T | Write T values | Consumer of values |
? | Read as Object | Inspection without type concern |
Use ? super T when the method adds values of type T to the collection and the caller may pass a collection of any supertype. This is common in factory methods, builders, and utility functions that populate collections. For example, a method that adds events to a history list should accept List<? super Event> so callers can pass List<Object> or List<Event>.
Use ? extends T when the method reads values and the caller may pass a collection of any subtype. Use an unbounded wildcard when the method only needs to inspect the collection without caring about element types, such as checking size() or iterating to call toString().
The decision is not about which wildcard is more powerful but about which accurately describes the method's contract. A method that both reads and writes T values should use a concrete type parameter T rather than a wildcard, because neither ? extends T nor ? super T permits both operations safely. When a method genuinely consumes T values and the caller benefits from passing a wider collection type, the ? super T bound is the correct tool.