Java ? super: Lower-Bounded Wildcards Explained
java ? super: Understand Java's ? super wildcard for lower-bounded generics, when to use it, and how it differs from ? extends.
java ? super requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
What ? super Means in Java Generics
In Java generics, ? super declares a lower-bounded wildcard. When you write List<? super Integer>, you are saying that the list can hold Integer or any supertype of Integer, such as Number or Object. This is the opposite of ? extends, which restricts the type to Integer or any of its subtypes.
The key consequence is that ? super allows safe writes to a generic collection. You can add elements of the lower-bound type or any subtype of it, but you cannot read elements as a specific type because the actual type parameter is unknown.
A Minimal Example of ? super
Consider a method that adds a series of integers to a list. Using ? super Integer ensures the method can accept a List<Integer>, List<Number>, or List<Object>:
public static void addIntegers(List<? super Integer> list) { for (int i = 0; i < 10; i++) { list.add(i); } }
This works because Integer is a subtype of ? super Integer's actual type, so the compiler knows that adding an Integer is safe. If you tried to read from list and assign to an Integer, the compiler would reject it, because the list might contain Object or Number elements.
The PECS Principle: Producer Extends, Consumer Super
? super is most useful when your method consumes elements from a generic structure. The acronym PECS (Producer Extends, Consumer Super) captures this: use ? extends when you only read from a structure, and ? super when you only write to it.
For example, a method that copies elements from a source list into a destination list can use ? super for the destination:
public static <T> void copy(List<? extends T> src, List<? super T> dest) { for (T item : src) { dest.add(item); } }
Here, src is a producer of T instances, so it uses ? extends T. The dest list is a consumer of T instances, so it uses ? super T. This signature allows copying from a List<Integer> to a List<Number>, which would not be possible with a plain List<T>.
When to Use ? super vs ? extends
The choice between ? super and ? extends depends on the direction of data flow. If your code only reads elements, ? extends gives you more flexibility because it accepts any subtype. If your code only writes elements, ? super is the right choice because it accepts any supertype, and you can safely add the lower bound's subtypes.
A common mistake is using ? extends for a method that needs to add elements. For example:
// Compilation error: cannot add Integer to List<? extends Number> public static void addOne(List<? extends Number> list) { list.add(1); }
The compiler rejects this because list might be a List<Double> or List<Float>, where adding an Integer would break type safety. Using ? super Integer instead allows the addition because Integer is a subtype of the lower bound.
Common Pitfalls with ? super
One pitfall is assuming that ? super allows reading as Object. While you can read elements as Object, you cannot assign them to a more specific type without an explicit cast. This limitation is intentional: the actual type parameter is unknown, so the only safe read type is Object.
Another issue is mixing ? super with generic methods unnecessarily. In many cases, a plain type parameter T is simpler and more flexible. For instance, public static <T> void addToList(List<T> list, T item) is clearer than public static void addToList(List<? super T> list, T item) when you control the type parameter. Use ? super when you need to accept a range of supertypes without fixing the type parameter.
Runtime Behavior and Type Erasure
At runtime, Java erases generic type information. A List<? super Integer> becomes a raw List after compilation, so there is no runtime distinction between ? super and ? extends. The compiler enforces the rules at compile time, but the JVM sees only raw types. This means that using ? super does not add any runtime overhead or change the underlying data structure. The benefit is purely compile-time type safety.
Because of type erasure, you cannot use ? super in a class definition or as a type argument for a class instance. For example, class MyClass<? super T> is illegal. Wildcards are only allowed in method signatures, field declarations, and local variables where the exact type is not needed.
Production Considerations for API Design
When designing a public API, ? super can make your methods more flexible without sacrificing type safety. For example, a utility method that fills a collection with default values can accept Collection<? super Integer>, allowing callers to pass List<Number> or Set<Object>. This flexibility reduces the need for callers to cast or create new collections.
However, overusing wildcards can make signatures harder to read. If a method only works with Integer elements, a plain List<Integer> is clearer. Reserve ? super for cases where you genuinely need to accept supertypes, such as in generic collection-copy utilities or event handlers that write to a shared buffer.
Maintainability also improves when you follow PECS consistently. Future maintainers can quickly understand whether a method reads or writes by looking at the wildcard type. Documenting the intended direction of data flow in Javadoc further reduces ambiguity.