Back to Blog
Java

Java ? extends: Bounded Wildcards in Generics

java ? extends: Understand how `? extends` works in Java generics: when to use it, why it makes collections read-only, and how it enables covariance.

Java GenericsWildcardsType SafetyCovarianceBounded Wildcards
Java generics wildcard extends concept with a list containing subtypes of a number supertype

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

When you write List<? extends Number>, you are telling the compiler that the list holds some specific subtype of Number, but you don't know which one. This is the bounded wildcard with an upper bound, and it is the key to making generic types covariant in Java. Without it, List<Integer> is not a subtype of List<Number>, even though Integer is a subtype of Number. The wildcard ? extends bridges that gap, but it comes with a strict restriction: you can read from the collection, but you cannot safely add to it.

The Core Syntax and Its Meaning

The wildcard ? extends is used in type arguments to accept a family of types that share a common supertype. For example:

public void processNumbers(List<? extends Number> numbers) { for (Number n : numbers) { System.out.println(n.doubleValue()); } }

Here, processNumbers can accept a List<Integer>, List<Double>, or List<Number> itself. The compiler knows that every element in the list is at least a Number, so you can safely read elements as Number. This is the essence of covariance: the method is more flexible without sacrificing type safety.

The syntax uses ? as a placeholder for an unknown type, and extends defines the upper bound. The bound can be a class or an interface, and you can also use multiple bounds in a type parameter declaration, but not in a wildcard. For wildcards, only a single bound is allowed.

Why ? extends Makes the Collection Read-Only

The most surprising behavior for many developers is that you cannot add elements to a List<? extends Number>. Consider this code:

List<? extends Number> numbers = new ArrayList<Integer>(); numbers.add(42); // compile error

The compiler rejects the add call because the actual type of the list is unknown. It could be List<Integer>, List<Double>, or any other subtype of Number. If you tried to add a Double to a List<Integer>, you would corrupt the list's type safety. Since the compiler cannot verify that the element you are adding is compatible with the unknown concrete type, it forbids all writes. This is why a wildcard with an upper bound is often described as a producer: you can only read from it.

This restriction is not a bug; it is the price of covariance. In exchange for accepting any subtype of the bound, you lose the ability to insert elements. The add method signature is void add(E element), and with E being the unknown type, the compiler cannot guarantee that the provided element is a subtype of that unknown type.

Practical Usage: Reading Data from Heterogeneous Collections

The most common use case for ? extends is writing a method that reads from a collection without modifying it. For instance, a method that sums all numbers in a list:

public static double sum(List<? extends Number> numbers) { double total = 0.0; for (Number n : numbers) { total += n.doubleValue(); } return total; }

This method works with List<Integer>, List<Double>, and even a List<Number>. Without the wildcard, you would need overloads for every numeric type or use raw types, both of which are worse. The wildcard gives you a single implementation that is both type-safe and flexible.

Another common pattern is using ? extends in method parameters that accept a collection of objects with a common interface. For example, a method that prints the names of all Shape objects:

public void printShapes(List<? extends Shape> shapes) { for (Shape s : shapes) { System.out.println(s.getName()); } }

You can pass a List<Circle>, List<Rectangle>, or List<Shape>. The method only reads, so the wildcard is appropriate.

Common Mistakes and Compile-Time Errors

One frequent mistake is trying to write to a ? extends collection. Developers often assume that since Integer is a subtype of Number, they can add an Integer to a List<? extends Number>. That fails for the reason explained earlier: the list might be a List<Double>. Another mistake is using ? extends when you actually need to add elements. If a method must both read and write, you need a fixed type parameter, not a wildcard.

Consider this incorrect method:

public void addIfPositive(List<? extends Number> numbers) { numbers.add(1); // error: cannot add to List<? extends Number> }

The compiler error is clear: add(capture#1 of ? extends Number) cannot be applied to int. The fix is to use a type parameter:

public <T extends Number> void addIfPositive(List<T> numbers) { T value = numbers.get(0); if (value.doubleValue() > 0) { numbers.add(value); // OK } }

Here, T is a concrete type that the method knows about, so it can read and write. The wildcard is not the right tool when you need to modify the collection.

? extends vs. ? super: Choosing the Right Wildcard

Java also provides a lower-bounded wildcard: ? super T. While ? extends T allows you to read items as T, ? super T allows you to write items of type T into the collection. The mnemonic is PECS: Producer Extends, Consumer Super. If a method only reads from a collection, use ? extends. If it only writes, use ? super. If it does both, use a concrete type parameter.

Here is a quick comparison:

WildcardBoundCan read asCan writeTypical use
? extends TUpper boundTNoReading from a collection
? super TLower boundObjectYes, elements of type TWriting to a collection
T (type parameter)NoneTYes, elements of type TRead and write

For example, a method that copies elements from a source list to a destination list might use ? extends for the source and ? super for the destination:

public static <T> void copy(List<? extends T> src, List<? super T> dest) { for (T item : src) { dest.add(item); } }

This is exactly how Collections.copy is declared. The source is a producer, so it uses ? extends T; the destination is a consumer, so it uses ? super T. This allows copying from a List<Integer> to a List<Number>, which would not be possible with fixed types.

Runtime Behavior and Type Erasure

At runtime, the JVM erases generic types. A List<? extends Number> is just a List to the runtime. The wildcard exists only at compile time to enforce type safety. This means that using ? extends does not add any runtime overhead. The compiler inserts implicit casts when you read elements, and those casts are safe because the compiler has verified the type bound. However, you can still get a ClassCastException if you use raw types or unchecked casts elsewhere in your code. The wildcard itself does not protect against unsafe code that bypasses generics.

Another runtime consideration is that you cannot create arrays of parameterized types with wildcards. For example, new List<? extends Number>[10] is illegal. This is because arrays are reified, and the JVM needs to know the exact component type at runtime, which is impossible with a wildcard. Use a List<List<? extends Number>> instead.

Production Considerations and Maintainability

Using ? extends in public APIs improves flexibility and communicates intent. When a method parameter is List<? extends Number>, it tells callers that the method will not modify the list. This is a form of documentation that the compiler enforces. It also prevents accidental mutations that could break the caller's assumptions.

However, overusing wildcards can make APIs harder to read. If a method signature becomes a maze of wildcards, consider whether a type parameter is clearer. For example, a method that only needs to read a list of a specific type might be simpler with List<T> if the caller expects a specific type. The wildcard is most valuable when you need to accept a family of types, not when you need to operate on a single type.

Another maintainability concern is that wildcards can obscure the exact type relationships in complex nested generics. For instance, Map<String, List<? extends Number>> is legal but can be confusing. In such cases, defining a type alias or a helper interface can improve readability. The key is to use wildcards where they solve a real flexibility problem, not just because they look advanced.

Finally, remember that ? extends does not make a collection immutable. It only prevents you from adding elements through that particular reference. If you have another reference with a concrete type, you can still modify the underlying collection. For example:

List<Integer> ints = new ArrayList<>(); List<? extends Number> nums = ints; nums.add(1); // error ints.add(2); // OK

This is important to understand when reasoning about thread safety or concurrent modifications. The wildcard is a compile-time view, not a runtime lock.

java ? extends: Practical Usage and Code Examples | RYUSLOG DEV