Java Generic Super Bound: Using ? super T
java generic super bound: Learn how the Java generic super bound (? super T) works, 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 can hold 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 list to Integer or its subtypes. The java generic super bound 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 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 type guaranteed is Object, because the actual element type could be Number or Object. Reading an element as Integer would require an unchecked cast, which the compiler refuses to allow 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 allows copying from List<Integer> to List<Number>:
List<Integer> integers = List.of(1, 2, 3); List<Number> numbers = new ArrayList<>(); copy(integers, numbers);
Without the lower bound on the target, this call would not compile. 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 identical to what you would get with a raw type or an 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 a supertype.
List<? super Integer> list = new ArrayList<Number>(); // list.add("text"); // does not compile
Adding a String fails because String is not guaranteed to be a supertype of Integer. The compiler only permits adding Integer or null, because Integer is the lower bound. Any other type could violate the contract if the actual collection were, for example, a List<Number>.
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.