Back to Blog
Java

Java Lower Bounded Wildcards: When to Use ? super T

java lower bounded wildcard: Learn how Java lower bounded wildcards (? super T) work, when to use them, and how they differ from upper bounds in generic methods.

java genericswildcardspecstype safetycollections
Diagram showing a Java list with a lower bounded wildcard accepting Integer and its supertypes Number and Object.

java lower bounded wildcard requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

The Problem Lower Bounded Wildcards Solve

When you declare a generic method parameter, the type must match exactly unless you introduce a wildcard. Consider a method that adds elements to a list:

public static void addNumbers(List<Integer> numbers, Integer value) { numbers.add(value); }

This works only for List<Integer>. If you have a List<Number> or a List<Object>, you cannot pass it, even though adding an Integer to either is perfectly safe. The compiler rejects the call because List<Number> is not a subtype of List<Integer>.

A lower bounded wildcard — written as ? super T — is the mechanism Java provides for this situation. It changes the method signature so the parameter accepts any list whose element type is Integer or a supertype of Integer:

public static void addNumber(List<? super Integer> numbers, Integer value) { numbers.add(value); }

Now the method accepts List<Integer>, List<Number>, and List<Object>. The wildcard ? super Integer means "some unknown type that is Integer or a supertype of Integer." That unknown type is what makes the parameter flexible.

Syntax and Placement

The syntax is straightforward:

List<? super Integer>

The wildcard ? is followed by the super keyword and a type. The type can be a class, an interface, or another type parameter. For example:

List<? super Integer> list = new ArrayList<Number>(); List<? super Integer> list2 = new ArrayList<Object>();

You cannot assign a List<String> to List<? super Integer> because String is neither Integer nor a supertype of Integer. The compiler enforces this at compile time.

Lower bounded wildcards appear in method parameters, field declarations, and local variable declarations. They are not useful in return types: if a method returns List<? super Integer>, the caller cannot safely read anything except Object from it, which defeats the purpose of returning a typed collection.

What You Can and Cannot Do

The restriction on reading is the key tradeoff. Given:

List<? super Integer> list = new ArrayList<Number>();

You can add an Integer:

list.add(42);

You can also add any subtype of Integer. There are none in the standard library besides Integer itself, but a custom subclass would work. You cannot add a Number that is not an Integer, because the list's actual type parameter is unknown. It might be List<Integer>, in which case adding a Double would violate type safety.

When reading, the compiler knows only that the element type is Object or some supertype of Integer. Since Object is the common supertype of everything, you can read elements only as Object:

Object first = list.get(0);

You cannot assign the result to Integer without a cast, and a cast is unsafe because the list might actually contain Number or Object instances.

This asymmetry — write access to Integer and its subtypes, read access only to Object — is what makes lower bounded wildcards useful for consumers.

PECS: Producer Extends, Consumer Super

The PECS rule, popularized by Joshua Bloch in Effective Java, states:

  • Use ? extends T when the collection produces values, meaning you read from it.
  • Use ? super T when the collection consumes values, meaning you write to it.

A method that copies elements from one list to another demonstrates both sides:

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

The destination list only receives values, so it uses ? super T. The source list only provides values, so it uses ? extends T. This is exactly the signature Collections.copy uses.

The same principle applies to a method that fills a list with a default value:

public static <T> void fill(List<? super T> list, T value) { for (int i = 0; i < list.size(); i++) { list.set(i, value); } }

Calling fill with a List<Object> and an Integer value works because Object is a supertype of Integer.

Practical Examples

The standard library uses lower bounded wildcards in several places. Collections.addAll is one:

public static <T> boolean addAll(Collection<? super T> c, T... elements)

This lets you add elements to a collection whose type is a supertype of the element type:

List<Number> numbers = new ArrayList<>(); Collections.addAll(numbers, 1, 2.5, 3L);

Each argument is autoboxed to Integer, Double, and Long, all of which are subtypes of Number. The wildcard ? super Number accepts List<Number> and List<Object>.

Another example is TreeSet's constructor that accepts a comparator:

public TreeSet(Comparator<? super E> comparator)

A TreeSet<Integer> can accept a Comparator<Number> because Number is a supertype of Integer. This is useful when you have a comparator written against a common supertype and want to reuse it for multiple specific types.

Common Mistakes

A frequent mistake is using a lower bounded wildcard where an upper bound is needed, or vice versa. If a method only reads from a collection and returns a value, ? super T forces the caller to read as Object, which is usually wrong:

public static <T> T first(List<? super T> list) { return (T) list.get(0); // unsafe cast }

The cast is unsafe because the list might contain a supertype instance. The correct signature for reading is ? extends T:

public static <T> T first(List<? extends T> list) { return list.get(0); }

Another mistake is using a lower bounded wildcard in a return type. A method that returns List<? super Integer> forces every caller to treat the result as a list of unknown supertype, which removes useful type information. Return types should use concrete types or upper bounds.

A third mistake is trying to add a supertype instance to a lower bounded collection:

List<? super Integer> list = new ArrayList<Number>(); list.add(3.14); // compile error

The compiler rejects this because the actual type parameter might be Integer, and a Double is not an Integer. The wildcard only guarantees that Integer and its subtypes can be added.

Type Erasure and Runtime Behavior

At runtime, all generic type information is erased. A List<? super Integer> and a List<Integer> both become raw ArrayList instances. The wildcard exists entirely at compile time; it has no runtime representation and no performance cost.

The practical consequence is that the compiler performs all the safety checks. Once the code compiles, the lower bound has no effect on the bytecode. This means you cannot use reflection to discover that a parameter was declared with a lower bound — the erasure of ? super Integer is simply Object, which is the same erasure as an unbounded wildcard.

One subtle runtime-related point: because the wildcard is erased, the only way to preserve type information across a method boundary is through the method's type parameter. That is why methods that use lower bounds are almost always generic methods with a type parameter T that appears both in the bound and in the parameter list.

When a Lower Bound Is the Wrong Choice

A lower bounded wildcard is not always the right tool. If you need to both read and write values of a specific type, a concrete type parameter is clearer:

public static <T> void process(List<T> list, T value) { list.add(value); T first = list.get(0); }

Here T gives full read and write access. A lower bound would force reads to Object and make the method harder to use.

If you only read, use ? extends T. If you only write, use ? super T. If you do both with the same type, use a type parameter. This decision rule covers most real-world cases and avoids the awkwardness of casting from Object.

java lower bounded wildcard: Practical Usage and Code Exampl | RYUSLOG DEV