Java Upper-Bounded Wildcards (? extends)
Java Upper-Bounded Wildcards (? extends)
An upper-bounded wildcard ? extends T means "some unknown type that is T or a subtype of T." Where the unbounded wildcard ? says "any type at all," ? extends Number says "some type that is at most Number — could be Integer, Double, Float, or Number itself, but nothing outside that family." This makes a method that accepts List<? extends Number> work with List<Integer>, List<Double>, and List<Float> — impossible with a plain List<Number> because of generic invariance. The key insight is what the bound trades: you gain the ability to accept all subtypes of T, and you give up the ability to add anything to the collection.
What Is an Upper-Bounded Wildcard?
An upper-bounded wildcard ? extends T is a type argument that represents an unknown type constrained to be T or any subtype of T. The ? is the wildcard (unknown type), and extends T is the upper bound — nothing above T in the hierarchy, and anything at or below it is allowed.
SYNTAX: List<? extends Number> "a list of some type that is Number or a subtype" Collection<? extends Comparable<String>> "a collection of types comparable to String" Map<String, ? extends Number> "a map from String to some numeric type" WHAT TYPES SATISFY ? extends Number: List<Number> -> yes (Number itself satisfies extends Number) List<Integer> -> yes (Integer is a subtype of Number) List<Double> -> yes (Double is a subtype of Number) List<Float> -> yes (Float is a subtype of Number) List<Long> -> yes List<String> -> NO (String does not extend Number) List<Object> -> NO (Object is a supertype of Number, not a subtype) WHAT YOU CAN DO WITH List<? extends Number>: Read -> Number element = list.get(i); // safe - any subtype IS-A Number Write -> list.add(1.5); // COMPILE ERROR - cannot add anything
Basic Overview - What Upper-Bounded Wildcards Do and Why They Work
1. THE CORE TRADE-OFF: READ-ACCESS FOR ANY SUBTYPE
Fresher view : ? extends Number lets you READ from a list of
Integers, Doubles, or Floats using a Number variable.
You get a method that works with all numeric lists -
not just List<Number>, which almost never exists.
Deeper view : when you hold a List<? extends Number>, the compiler
knows every element IS-A Number (because the actual
list must contain a Number subtype). Reading via
a Number reference is always safe. But WRITING fails
because the compiler cannot verify WHICH subtype the
list holds - you might be adding an Integer to what
is actually a List<Double>, corrupting it.
2. WHY add() IS DISALLOWED
Fresher view : imagine you hold a List<? extends Number>. It might
be a List<Integer> or a List<Double>. Adding 3.14
to what might be a List<Integer> would break it.
Adding 42 to what might be a List<Double> would also
be suspicious. The compiler disables all writes to
keep both cases safe.
Deeper view : the type of the unknown actual list is captured as
CAP#1 extends Number by the compiler. Adding any
element (even null for non-null cases) would require
the element to be exactly CAP#1, which is unknown.
Only null satisfies every unknown reference type.
This is why add(null) is the one exception.
3. PECS: PRODUCER EXTENDS, CONSUMER SUPER
Fresher view : use ? extends T when the collection PRODUCES values
for you to read. Use ? super T when the collection
CONSUMES values you write into it.
"Producer Extends" is the phrase to remember.
Deeper view : PECS (Producer Extends, Consumer Super) is the
principle that guides wildcard selection at API
design time. A list that your method reads from is
a "producer" of elements - it produces values for
your code. A list that your method writes into is
a "consumer" of elements. Extends = producer = read.
Super = consumer = write. Lower-bounded wildcards
(? super T) are the consumer side and are covered
in the Lower-Bounded Wildcards article.
4. ? extends T vs T extends T (BOUNDED TYPE PARAMETER)
Fresher view : the method declaration <T extends Number> at the
method signature gives T a name you can reuse.
? extends Number at a parameter position means you
do not need to reuse the name - just read.
Deeper view : <T extends Number> creates a named type variable
allowing T to appear in the return type, other
parameters, or the body. ? extends Number creates
an anonymous bound - the actual type is captured
by the compiler but has no user-accessible name.
Use ? extends T when the return type is unrelated
to the element type; use <T extends T> when you
need to refer to T in multiple places.
Why Upper-Bounded Wildcards Are Needed
The same invariance problem that motivated the plain wildcard ? applies here, but with a more specific need: sometimes a method should work with any collection of Number subtypes, not just Number itself, and it needs to call Number methods on the elements it reads.
1// File: WhyUpperBoundedDemo.java
2
3import java.util.List;
4
5public class WhyUpperBoundedDemo {
6
7 // ATTEMPT 1: accept List<Number> directly
8 // Problem: List<Integer> is NOT a List<Number> (invariance)
9 // A caller with List<Integer> cannot use this method
10 static double sumV1(List<Number> numbers) {
11 double total = 0;
12 for (Number n : numbers) {
13 total += n.doubleValue();
14 }
15 return total;
16 }
17
18 // ATTEMPT 2: accept List<?> - the unbounded wildcard
19 // Problem: elements come out as Object - cannot call doubleValue()
20 static double sumV2(List<?> items) {
21 double total = 0;
22 for (Object item : items) {
23 // total += item.doubleValue(); // COMPILE ERROR - Object has no doubleValue()
24 // Would need a cast - losing compile-time safety
25 }
26 return total;
27 }
28
29 // CORRECT: accept List<? extends Number>
30 // Reads any Number subtype, calls Number methods safely
31 static double sum(List<? extends Number> numbers) {
32 double total = 0;
33 for (Number n : numbers) { // elements come out as Number - not Object
34 total += n.doubleValue(); // Number method - available without cast
35 }
36 return total;
37 }
38
39 public static void main(String[] args) {
40
41 List<Integer> intAmounts = List.of(499, 1299, 799);
42 List<Double> doubleAmounts = List.of(499.0, 1299.0, 799.0);
43 List<Long> longAmounts = List.of(499L, 1299L, 799L);
44
45 System.out.println("=== sum(List<? extends Number>) accepts all numeric lists ===");
46 System.out.println("Integer list sum: Rs." + sum(intAmounts));
47 System.out.println("Double list sum: Rs." + sum(doubleAmounts));
48 System.out.println("Long list sum: Rs." + sum(longAmounts));
49
50 System.out.println();
51
52 System.out.println("=== List<Number> approach fails at compile time ===");
53 // sumV1(intAmounts); // COMPILE ERROR
54 // "incompatible types: List<Integer> cannot be converted to List<Number>"
55 // sumV1(doubleAmounts); // COMPILE ERROR - same invariance issue
56 System.out.println("List<Number> only works for actual List<Number> - rarely created");
57
58 System.out.println();
59
60 System.out.println("=== add() is disallowed on List<? extends Number> ===");
61 List<Integer> mutableInts = new java.util.ArrayList<>(List.of(10, 20, 30));
62 List<? extends Number> readableList = mutableInts;
63
64 Number first = readableList.get(0); // reading is fine - comes out as Number
65 System.out.println("Read from wildcard: " + first);
66
67 // readableList.add(40); // COMPILE ERROR
68 // readableList.add(3.14); // COMPILE ERROR
69 // The compiler rejects any add() because it cannot verify the subtype
70 System.out.println("add() rejected by compiler - read-only contract enforced");
71 }
72}Output:
=== sum(List<? extends Number>) accepts all numeric lists ===
Integer list sum: Rs.2597.0
Double list sum: Rs.2597.0
Long list sum: Rs.2597.0
=== List<Number> approach fails at compile time ===
List<Number> only works for actual List<Number> - rarely created
=== add() is disallowed on List<? extends Number> ===
Read from wildcard: 10
add() rejected by compiler - read-only contract enforced
Reading From Upper-Bounded Wildcards
Elements read from a List<? extends T> come out typed as T, not as Object. This is the key capability that distinguishes ? extends T from the plain unbounded wildcard ? — ? gives you Object, while ? extends Number gives you Number, with all of Number's methods available without a cast.
1// File: UpperBoundReadingDemo.java
2
3import java.util.List;
4
5public class UpperBoundReadingDemo {
6
7 // Statistics methods that work for any numeric list
8 static double average(List<? extends Number> values) {
9 if (values.isEmpty()) return 0.0;
10 double sum = 0;
11 for (Number value : values) {
12 sum += value.doubleValue(); // Number method - no cast
13 }
14 return sum / values.size();
15 }
16
17 static double max(List<? extends Number> values) {
18 if (values.isEmpty()) throw new java.util.NoSuchElementException();
19 double result = values.get(0).doubleValue();
20 for (Number value : values) {
21 double d = value.doubleValue();
22 if (d > result) result = d;
23 }
24 return result;
25 }
26
27 static double min(List<? extends Number> values) {
28 if (values.isEmpty()) throw new java.util.NoSuchElementException();
29 double result = values.get(0).doubleValue();
30 for (Number value : values) {
31 double d = value.doubleValue();
32 if (d < result) result = d;
33 }
34 return result;
35 }
36
37 // Works with ? extends Comparable<T> - for any type that can compare itself
38 static <T extends Comparable<T>> T maxComparable(List<? extends T> items) {
39 if (items.isEmpty()) throw new java.util.NoSuchElementException();
40 T result = items.get(0); // T - not Object
41 for (T item : items) {
42 if (item.compareTo(result) > 0) result = item;
43 }
44 return result; // returns T - caller gets the actual type
45 }
46
47 public static void main(String[] args) {
48
49 List<Integer> scores = List.of(72, 88, 95, 64, 91);
50 List<Double> ratings = List.of(4.2, 3.8, 4.9, 4.1, 3.5);
51 List<Long> revenue = List.of(450000L, 720000L, 380000L, 910000L);
52
53 System.out.println("=== average() with different numeric types ===");
54 System.out.printf("Average score : %.2f%n", average(scores));
55 System.out.printf("Average rating : %.2f%n", average(ratings));
56 System.out.printf("Average revenue: Rs.%.2f%n", average(revenue));
57
58 System.out.println();
59
60 System.out.println("=== max() and min() ===");
61 System.out.println("Highest score : " + max(scores));
62 System.out.println("Lowest rating : " + min(ratings));
63
64 System.out.println();
65
66 System.out.println("=== maxComparable() - works for any Comparable type ===");
67 List<String> cities = List.of("Bengaluru", "Mumbai", "Delhi", "Hyderabad");
68 String lastCity = maxComparable(cities); // T inferred as String
69 System.out.println("Last city (lexicographically): " + lastCity);
70
71 // Notice: maxComparable returns T (String here) - not Object
72 // The caller gets the actual type without any cast
73 }
74}Output:
=== average() with different numeric types ===
Average score : 82.00
Average rating : 4.10
Average revenue: Rs.615000.00
=== max() and min() ===
Highest score : 95.0
Lowest rating : 3.5
=== maxComparable() - works for any Comparable type ===
Last city (lexicographically): Mumbai
The PECS Principle - Producer Extends
PECS stands for Producer Extends, Consumer Super. It is the design rule that tells you which wildcard to use at any API boundary. A collection is a "producer" when your code reads elements out of it — the collection produces values for your algorithm. A collection is a "consumer" when your code writes elements into it — your algorithm consumes the values it produces into the collection.
PRODUCER EXTENDS — the collection gives data TO your method:
static double sum(List<? extends Number> numbers)
^^^^^^^^^^^^^^^^^
PRODUCER - your method reads from it
The list PRODUCES Number values for the sum calculation.
The method reads only. ? extends is correct.
CONSUMER SUPER — your method puts data INTO the collection:
(covered in Lower-Bounded Wildcards article)
static void fill(List<? super Integer> dest, Integer value)
^^^^^^^^^^^^^^^^^^^
CONSUMER - your method writes to it
The list CONSUMES Integer values from the fill operation.
The method writes only. ? super is correct.
A METHOD CAN HAVE BOTH — one param reads, another writes:
static <T> void copy(List<? super T> dest, List<? extends T> src)
// src PRODUCES T values (read from it) -> ? extends
// dest CONSUMES T values (write to it) -> ? super
// This is exactly the signature of Collections.copy()
THE PRACTICAL RULE:
Before adding a wildcard, ask: does this parameter go IN or come OUT?
Comes OUT of the collection (you read it)? -> ? extends T (Producer Extends)
Goes INTO the collection (you write it)? -> ? super T (Consumer Super)
Both? -> Probably needs a named <T>
Neither (structural ops only)? -> plain ? or no wildcard
1// File: PecsProducerDemo.java
2
3import java.util.List;
4import java.util.ArrayList;
5
6public class PecsProducerDemo {
7
8 // PRODUCER: src PRODUCES elements (we read from it)
9 // src is the producer -> ? extends T
10 // dest is the consumer -> ? super T (lower-bounded - consumer side)
11 static <T> void copyAll(List<? super T> dest, List<? extends T> src) {
12 for (T element : src) { // reading from producer (? extends T) -> T
13 dest.add(element); // writing to consumer (? super T) -> accepts T
14 }
15 }
16
17 // All these work as PRODUCERS (read-only access):
18 static double computeTotal(List<? extends Number> amounts) {
19 return amounts.stream().mapToDouble(Number::doubleValue).sum();
20 }
21
22 // A method that shows BOTH sides - reading from one, writing to another
23 static List<Double> toDoubles(List<? extends Number> source) {
24 List<Double> result = new ArrayList<>();
25 for (Number n : source) { // source PRODUCES Number values
26 result.add(n.doubleValue()); // result is our own List<Double> - we control it
27 }
28 return result;
29 }
30
31 public static void main(String[] args) {
32
33 System.out.println("=== copyAll - Producer (? extends) + Consumer (? super) ===");
34 List<Integer> intSource = List.of(499, 1299, 799);
35 List<Number> numDest = new ArrayList<>();
36
37 copyAll(numDest, intSource); // dest=List<Number>, src=List<Integer>
38 System.out.println("Copied to List<Number>: " + numDest);
39
40 List<Object> objDest = new ArrayList<>();
41 copyAll(objDest, intSource); // dest=List<Object>, src=List<Integer>
42 System.out.println("Copied to List<Object>: " + objDest);
43
44 System.out.println();
45
46 System.out.println("=== computeTotal - producer reading all numeric types ===");
47 List<Double> orderAmounts = List.of(799.0, 1299.0, 499.0, 2499.0);
48 List<Integer> itemCounts = List.of(3, 1, 5, 2);
49
50 System.out.printf("Order total : Rs.%.2f%n", computeTotal(orderAmounts));
51 System.out.printf("Item total : %.2f%n", computeTotal(itemCounts));
52
53 System.out.println();
54
55 System.out.println("=== toDoubles - converting any numeric list to List<Double> ===");
56 List<Integer> intPrices = List.of(499, 1299, 799);
57 List<Double> asDoubles = toDoubles(intPrices);
58 System.out.println("Integer prices as Doubles: " + asDoubles);
59 }
60}Output:
=== copyAll - Producer (? extends) + Consumer (? super) ===
Copied to List<Number>: [499, 1299, 799]
Copied to List<Object>: [499, 1299, 799]
=== computeTotal - producer reading all numeric types ===
Order total : Rs.5096.00
Item total : 11.00
=== toDoubles - converting any numeric list to List<Double> ===
Integer prices as Doubles: [499.0, 1299.0, 799.0]
Upper-Bounded Wildcards on Non-List Types
Upper-bounded wildcards work the same way on any generic type — Map, Optional, custom generic classes, or generic interfaces.
1// File: NonListWildcardDemo.java
2
3import java.util.*;
4
5public class NonListWildcardDemo {
6
7 // Works with any Map whose values are Number subtypes
8 static double sumMapValues(Map<String, ? extends Number> map) {
9 double total = 0;
10 for (Number value : map.values()) { // values() returns Collection<? extends Number>
11 total += value.doubleValue();
12 }
13 return total;
14 }
15
16 // Works with Optional holding any Number subtype
17 static double getValueOrZero(Optional<? extends Number> opt) {
18 return opt.map(Number::doubleValue).orElse(0.0);
19 }
20
21 // Works with any Iterable of Comparable elements
22 static <T extends Comparable<T>> T findMin(Iterable<? extends T> items) {
23 T min = null;
24 for (T item : items) {
25 if (min == null || item.compareTo(min) < 0) min = item;
26 }
27 if (min == null) throw new NoSuchElementException("Iterable is empty");
28 return min;
29 }
30
31 public static void main(String[] args) {
32
33 System.out.println("=== sumMapValues - Map<String, ? extends Number> ===");
34 Map<String, Integer> intMap = new LinkedHashMap<>();
35 intMap.put("Electronics", 42500);
36 intMap.put("Clothing", 18300);
37 intMap.put("Home", 11200);
38
39 Map<String, Double> doubleMap = new LinkedHashMap<>();
40 doubleMap.put("Q1", 125000.0);
41 doubleMap.put("Q2", 148000.0);
42 doubleMap.put("Q3", 132000.0);
43
44 System.out.printf("Integer map total: Rs.%.0f%n", sumMapValues(intMap));
45 System.out.printf("Double map total : Rs.%.0f%n", sumMapValues(doubleMap));
46
47 System.out.println();
48
49 System.out.println("=== getValueOrZero - Optional<? extends Number> ===");
50 Optional<Integer> presentInt = Optional.of(2499);
51 Optional<Double> presentDouble = Optional.of(3.14);
52 Optional<Number> empty = Optional.empty();
53
54 System.out.println("Integer Optional: " + getValueOrZero(presentInt));
55 System.out.println("Double Optional : " + getValueOrZero(presentDouble));
56 System.out.println("Empty Optional : " + getValueOrZero(empty));
57
58 System.out.println();
59
60 System.out.println("=== findMin - Iterable<? extends Comparable> ===");
61 // Works with TreeSet, LinkedList, or any Iterable
62 TreeSet<Integer> sortedScores = new TreeSet<>(List.of(72, 88, 95, 64, 91));
63 System.out.println("Min score: " + findMin(sortedScores));
64
65 List<String> cities = List.of("Mumbai", "Bengaluru", "Chennai", "Ahmedabad");
66 System.out.println("First city: " + findMin(cities));
67 }
68}Output:
=== sumMapValues - Map<String, ? extends Number> ===
Integer map total: Rs.72000
Double map total : Rs.405000
=== getValueOrZero - Optional<? extends Number> ===
Integer Optional: 2499.0
Double Optional : 3.14
Empty Optional : 0.0
=== findMin - Iterable<? extends Comparable> ===
Min score: 64
First city: Ahmedabad
Real-World Example - PhonePe Transaction Analytics
A payment platform's analytics service processes transaction amounts from multiple data sources — UPI transfers, wallet top-ups, bill payments, and merchant settlements — each reported as a different numeric type. Generic analytics methods that accept List<? extends Number> handle all of them without duplication, while methods that return typed statistics preserve the element type through the return value.
1// File: TransactionBatch.java
2
3import java.util.List;
4
5// Holds a batch of transaction amounts of some numeric type.
6// The numeric type is parameterized — UPI transfers might be Integer (paise),
7// wallet balances might be Double (rupees), etc.
8public class TransactionBatch<T extends Number> {
9
10 private final String channel;
11 private final List<T> amounts;
12
13 public TransactionBatch(String channel, List<T> amounts) {
14 this.channel = channel;
15 this.amounts = List.copyOf(amounts);
16 }
17
18 public String getChannel() { return channel; }
19 public List<T> getAmounts() { return amounts; }
20 public int size() { return amounts.size(); }
21}1// File: TransactionAnalyticsService.java
2
3import java.util.List;
4
5public class TransactionAnalyticsService {
6
7 // Computes total across any batch of numeric amounts.
8 // TransactionBatch<? extends Number> accepts TransactionBatch<Integer>,
9 // TransactionBatch<Double>, TransactionBatch<Long> etc.
10 public double computeTotal(TransactionBatch<? extends Number> batch) {
11 double total = 0;
12 for (Number amount : batch.getAmounts()) {
13 total += amount.doubleValue();
14 }
15 return total;
16 }
17
18 // Finds the largest single transaction across any numeric batch.
19 public double findPeak(TransactionBatch<? extends Number> batch) {
20 if (batch.size() == 0) return 0;
21 double peak = batch.getAmounts().get(0).doubleValue();
22 for (Number amount : batch.getAmounts()) {
23 double value = amount.doubleValue();
24 if (value > peak) peak = value;
25 }
26 return peak;
27 }
28
29 // Computes average transaction value.
30 public double computeAverage(TransactionBatch<? extends Number> batch) {
31 if (batch.size() == 0) return 0;
32 return computeTotal(batch) / batch.size();
33 }
34
35 // Accepts multiple batches - each can be a different numeric type.
36 // List<? extends TransactionBatch<? extends Number>> - a list of batches
37 // where each batch can hold any Number subtype.
38 public void printSummary(List<? extends TransactionBatch<? extends Number>> batches) {
39 System.out.println("+-----------------------------------------------------+");
40 System.out.println("| Transaction Analytics Summary |");
41 System.out.println("+-----------------------------------------------------+");
42
43 double grandTotal = 0;
44 for (TransactionBatch<? extends Number> batch : batches) {
45 double total = computeTotal(batch);
46 double peak = findPeak(batch);
47 double average = computeAverage(batch);
48 grandTotal += total;
49
50 System.out.printf("| %-15s | count=%-4d | total=Rs.%,-10.2f |%n",
51 batch.getChannel(), batch.size(), total);
52 System.out.printf("| | peak=Rs.%-10.2f| avg=Rs.%-10.2f |%n",
53 peak, average);
54 System.out.println("| +---------+-------------------+");
55 }
56
57 System.out.println("+-----------------------------------------------------+");
58 System.out.printf("| Grand Total: Rs.%,-34.2f |%n", grandTotal);
59 System.out.println("+-----------------------------------------------------+");
60 }
61}1// File: PhonePeAnalyticsDemo.java
2
3import java.util.List;
4
5public class PhonePeAnalyticsDemo {
6
7 public static void main(String[] args) {
8
9 // Each batch holds a different numeric type
10 TransactionBatch<Integer> upiTransfers = new TransactionBatch<>(
11 "UPI Transfers",
12 List.of(500, 1200, 3500, 800, 15000, 2500)
13 );
14
15 TransactionBatch<Double> walletTopups = new TransactionBatch<>(
16 "Wallet Top-ups",
17 List.of(200.0, 500.0, 1000.0, 2000.0, 500.0)
18 );
19
20 TransactionBatch<Long> merchantSettlements = new TransactionBatch<>(
21 "Merchant Settlements",
22 List.of(45000L, 120000L, 78000L, 230000L)
23 );
24
25 TransactionAnalyticsService service = new TransactionAnalyticsService();
26
27 System.out.println("=== Individual batch analytics ===");
28 System.out.printf("UPI total : Rs.%,.2f%n", service.computeTotal(upiTransfers));
29 System.out.printf("Wallet peak : Rs.%,.2f%n", service.findPeak(walletTopups));
30 System.out.printf("Settlement avg: Rs.%,.2f%n", service.computeAverage(merchantSettlements));
31
32 System.out.println();
33
34 System.out.println("=== Multi-channel summary ===");
35 // List of batches - each can be a different numeric type
36 service.printSummary(List.of(upiTransfers, walletTopups, merchantSettlements));
37 }
38}Output:
=== Individual batch analytics ===
UPI total : Rs.24,000.00
Wallet peak : Rs.2,000.00
Settlement avg: Rs.118,250.00
=== Multi-channel summary ===
+-----------------------------------------------------+
| Transaction Analytics Summary |
+-----------------------------------------------------+
| UPI Transfers | count=6 | total=Rs.24,000.00 |
| | peak=Rs.15,000.00 | avg=Rs.4,000.00 |
| +---------+-------------------+
| Wallet Top-ups | count=5 | total=Rs.4,200.00 |
| | peak=Rs.2,000.00 | avg=Rs.840.00 |
| +---------+-------------------+
| Merchant Settlements | count=4 | total=Rs.473,000.00 |
| | peak=Rs.230,000.00 | avg=Rs.118,250.00 |
| +---------+-------------------+
+-----------------------------------------------------+
| Grand Total: Rs.501,200.00 |
+-----------------------------------------------------+
computeTotal, findPeak, and computeAverage each accept TransactionBatch<? extends Number> — they receive the batch as a producer of Number values and read using doubleValue() without any cast. printSummary accepts List<? extends TransactionBatch<? extends Number>> — a nested wildcard meaning "a list of batches, each of which holds some Number subtype." All three batches with different numeric types pass to the same printSummary call, and the compiler accepts each one.
Upper-Bounded Wildcard - Quick Reference
| Aspect | List<? extends Number> | List<Number> | List<T extends Number> |
|---|---|---|---|
Accepts List<Integer> | Yes | No | Only when T=Integer |
Accepts List<Double> | Yes | No | Only when T=Double |
| Read element type | Number | Number | T (the specific subtype) |
add() allowed | No (except null) | Yes | Yes |
| Return type can reference element type | No (type is anonymous) | Yes | Yes, as T |
| Named type variable available | No | N/A | Yes, T |
| Typical use | Read-only processing of any numeric collection | Only when list truly holds Number instances | When T must appear in return type or other params |
Best Practices
Use ? extends T when a method reads from a collection and should accept any subtype of T. The pattern is: if your method calls methods on elements using the declared type T, the parameter should be List<? extends T>. If it only calls Object methods, plain ? suffices. If it needs to add elements, neither wildcard works — use a named type parameter.
Remember the PECS mnemonic at API design time. When designing a method signature, for each collection parameter, ask: does this method read from it (producer) or write to it (consumer)? Producer parameters get ? extends T. Consumer parameters get ? super T. This one question, applied consistently, produces correct wildcard choices without memorizing rules.
Do not use ? extends T when the method also needs to add elements. The read-only constraint is not negotiable — the compiler will reject every add() call. If a method must both read and write to a collection, use a named type parameter <T> rather than a wildcard, because the named type allows both operations and additionally makes the element type available in the return type.
Prefer ? extends T over casting inside the method body. A method that accepts List<Number> and then casts each element to Integer inside the body is doing two things wrong: rejecting List<Integer> from callers, and performing unchecked casts at runtime. List<? extends Number> eliminates both problems — it accepts any numeric list and reads each element safely as Number.
Common Mistakes
Mistake 1 - Trying to Add Elements to a List<? extends T>
1import java.util.List;
2import java.util.ArrayList;
3
4// WRONG - the compiler rejects every add() call on List<? extends Number>
5// because the actual list type is unknown. Adding 42 to a List<Double>
6// or adding 3.14 to a List<Integer> would corrupt the list.
7static void tryToAdd(List<? extends Number> numbers) {
8 // numbers.add(42); // COMPILE ERROR
9 // numbers.add(3.14); // COMPILE ERROR
10 // numbers.add(null); // compiles - null is always safe, but adds nothing useful
11}
12
13// CORRECT - if elements need to be added, use a named type parameter
14// The named type parameter <T extends Number> allows both reading and writing
15static <T extends Number> void addAndRead(List<T> numbers, T newValue) {
16 numbers.add(newValue); // legal - T is known at the call site
17 for (T n : numbers) {
18 System.out.println(n.doubleValue()); // Number method available via bound
19 }
20}Mistake 2 - Confusing ? extends T With T in the Return Type
1import java.util.List;
2
3// WRONG ASSUMPTION - a developer might try to return the element type
4// from a method declared with ? extends Number. The ? has no name,
5// so it cannot appear in the return type.
6static ? extends Number getFirst(List<? extends Number> numbers) {
7 // COMPILE ERROR: "illegal start of type" - ? cannot be a return type
8 return numbers.get(0);
9}
10
11// CORRECT OPTION A - return Number (the bound) when the specific subtype is irrelevant
12static Number getFirstAsNumber(List<? extends Number> numbers) {
13 return numbers.get(0); // returns Number - caller gets Number reference
14}
15
16// CORRECT OPTION B - use a named type parameter when the exact type matters
17// This lets the caller know the return type matches the list's element type
18static <T extends Number> T getFirstTyped(List<T> numbers) {
19 return numbers.get(0); // returns T - for List<Integer> returns Integer
20}Mistake 3 - Using ? extends T When the Subtype Must Be Preserved
1import java.util.ArrayList;
2import java.util.List;
3
4// WRONG - transforming a List<Integer> into a List<Integer> using ?
5// The wildcard loses the element type information in the return path
6static List<? extends Number> doubleAll(List<? extends Number> numbers) {
7 List<Number> result = new ArrayList<>(); // cannot use List<T> - no T
8 for (Number n : numbers) {
9 result.add(n.doubleValue() * 2); // adds Double to List<Number>
10 }
11 return result;
12 // A caller with List<Integer> gets back List<Number> - the Integer type is lost
13}
14
15// CORRECT - named type parameter preserves the element type through transformation
16static <T extends Number> List<Double> doubleAllTyped(List<T> numbers) {
17 List<Double> result = new ArrayList<>();
18 for (T n : numbers) {
19 result.add(n.doubleValue() * 2); // correctly converts to Double
20 }
21 return result; // clearly returns List<Double> regardless of input type
22}Mistake 4 - Applying ? extends Where an Unbounded ? Would Do
1import java.util.List;
2
3// UNNECESSARY - printAll only calls toString() implicitly via println.
4// toString() is on Object - no Number methods are called.
5// ? extends Number is over-specified and restricts callers unnecessarily.
6static void printAllNumbers(List<? extends Number> items) {
7 for (Number item : items) {
8 System.out.println(item); // only toString() is called - available on Object
9 }
10}
11
12// BETTER - use plain ? - accepts any list, communicates "we don't use the type"
13static void printAll(List<?> items) {
14 for (Object item : items) {
15 System.out.println(item); // Object reference is sufficient for println
16 }
17}
18
19// RESERVE ? extends T for when T-specific methods are actually called
20static double sumNumbers(List<? extends Number> items) {
21 double total = 0;
22 for (Number item : items) {
23 total += item.doubleValue(); // Number.doubleValue() - ? extends Number JUSTIFIED
24 }
25 return total;
26}Interview Questions
Q1. What does List<? extends Number> mean, and how does it differ from List
List<? extends Number> means "a list of some unknown type that is Number or a subtype of Number." It accepts List<Integer>, List<Double>, List<Float>, and List<Number> — any list whose element type is within the Number family. List<Number> accepts only an actual List<Number>. Because generic types are invariant, List<Integer> is not a List<Number> even though Integer extends Number, so a method declaring List<Number> cannot receive a List<Integer>. In a well-typed codebase, List<Number> is rarely created, making utility methods that use it practically useless for callers. List<? extends Number> solves this by accepting all numeric lists through the wildcard.
Q2. Why can you not add elements to a List<? extends Number>?
When you hold a List<? extends Number>, the compiler knows the list contains some specific Number subtype, but it does not know which one. The actual runtime list might be a List<Integer>, a List<Double>, or a List<Float>. Adding an Integer to what might be a List<Double> would corrupt the list; adding a Double to what might be a List<Integer> would equally corrupt it. Since the compiler cannot determine the correct element type at compile time, it disallows all add() calls (except null, which is always safe for any reference type) as a conservative safety measure. The read-only constraint is the direct consequence of the type being unknown.
Q3. What is PECS, and how does it guide the choice of wildcard?
PECS stands for Producer Extends, Consumer Super. It is the rule for choosing between ? extends T and ? super T in method parameters. A collection is a "producer" when your method reads values from it — it produces elements for your algorithm. In that case, use ? extends T. A collection is a "consumer" when your method writes values into it — your algorithm produces elements that the collection consumes. In that case, use ? super T. The classic example is Collections.copy(List<? super T> dest, List<? extends T> src): src is a producer (read from) so it uses ? extends T; dest is a consumer (written to) so it uses ? super T.
Q4. What does the element come out as when reading from a List<? extends Number>?
Elements read from List<? extends Number> come out typed as Number — the declared upper bound. This is the key advantage over the unbounded wildcard List<?>, where elements come out as Object. When elements come out as Number, all methods declared on Number — intValue(), doubleValue(), longValue(), floatValue() — are directly callable without any cast. This is why List<? extends Number> is the correct signature for methods that perform numeric computations: the bound makes Number methods available while the wildcard makes the method accept any numeric list.
Q5. When should you use ? extends T instead of a bounded type parameter
Use ? extends T when the element type does not need to be named or reused anywhere else in the same declaration — typically when a method reads from a collection and the return type is not related to the element type (returns double, boolean, void, or some other fixed type). Use a named bounded type parameter <T extends T> when the element type must appear in more than one position — in the return type (List<T> filter(...)), in another parameter (void process(List<T> items, T target)), or when the method body needs to create instances of T or pass T values to typed methods.
Q6. Can you nest upper-bounded wildcards, and what does List<? extends List<? extends Number>> mean?
Yes. List<? extends List<? extends Number>> means "a list of some type that is a List of Number subtypes or a subtype of that." In practice, this accepts List<List<Integer>>, List<List<Double>>, ArrayList<List<Long>>, and similar structures. The outer ? extends List<? extends Number> says the outer list holds some kind of List-of-numbers subtype; the inner ? extends Number says those inner lists hold numeric subtypes. Reading from the outer list gives you List<? extends Number>, and reading from that gives you Number. Adding to either is disallowed for the same reasons as any other ? extends usage.
FAQs
Does ? extends T work with interfaces as the bound?
Yes. List<? extends Comparable<String>> or List<? extends Runnable> are valid upper-bounded wildcards where the bound is an interface. The extends keyword in a wildcard bound covers both class extension and interface implementation, just as it does in bounded type parameters. ? extends Comparable<String> means "some type that implements Comparable<String>," and Comparable<String> methods are available on elements read from such a list.
What is the difference between List<? extends Number> and List<? extends Object>?
List<? extends Number> restricts the element type to the Number hierarchy — callers can pass List<Integer>, List<Double>, etc., and elements come out as Number. List<? extends Object> accepts any reference type list (since everything extends Object) and elements come out as Object — this is equivalent to the unbounded wildcard List<?>. The difference lies in which methods are available on the element: Number methods for the former, only Object methods for the latter.
Can ? extends be used in a class field declaration?
Yes, but with care. A field declared as List<? extends Number> values can hold any list whose elements are Number subtypes. You can read from the field as Number, but you cannot add to it through the field. This is useful when a class stores a collection of items whose exact type was provided at construction time and should not be modified through the class's own API. Records and immutable classes use this pattern to hold narrower-typed lists without knowing the exact element type.
Why does the compiler sometimes report CAP#1 in error messages for wildcards?
CAP#1 extends Number is the compiler's internal name for a captured wildcard type. When you pass a List<? extends Number> to a method, the compiler "captures" the unknown ? as a fresh type variable named CAP#1 for that call. You cannot use or reference CAP#1 in your code — it is purely internal. When you see it in an error message, it means the compiler is telling you that some operation (usually add()) fails because the element type is this unknown captured type, not any specific type you can name.
Is ? extends T a subtype of ? extends S when T is a subtype of S?
Yes. List<? extends Integer> is a subtype of List<? extends Number> because any list accepted by the former is also accepted by the latter — if the element type is Integer or a subtype of Integer, it is certainly a subtype of Number. This is covariance in wildcard types, and it is the mechanism that makes wildcard types composable in method parameter hierarchies.
Can an upper-bounded wildcard be combined with a named type parameter in the same method signature?
Yes — and this is one of the most useful patterns in generic API design. A method can have a named type parameter <T> for some role and a wildcard parameter List<? extends T> for another. For example, static <T> T findFirst(List<? extends T> items, T defaultValue) uses T for the return type and defaultValue parameter, while ? extends T lets the list hold any subtype of T. The wildcard in the list parameter enables covariance (accepting lists of subtypes), while the named T preserves the return type contract.
Summary
An upper-bounded wildcard ? extends T grants covariant read access to a generic collection: it accepts any parameterized collection whose element type is T or a subtype, and it provides elements to the method typed as T rather than Object. The trade-off is total: the method gains the ability to accept all subtypes of T in the collection, and it gives up the ability to write anything to the collection (except null).
PECS — Producer Extends, Consumer Super — is the one rule that governs upper-bounded wildcard use: if a collection parameter provides values to your algorithm (it is a producer of elements), bound it with ? extends T. If it accepts values from your algorithm (it is a consumer of elements), bound it with ? super T. If it does both, a named type parameter is probably the right choice.
The practical difference between List<? extends Number> and List<Number> is the difference between a utility method that works in the real world (where List<Integer> and List<Double> are what developers create) and one that is technically correct but practically unusable (because List<Number> almost never appears in well-typed code). Upper-bounded wildcards bridge that gap cleanly.
What to Read Next
Learn how to accept a type or any of its superclasses.