Java Tutorial
🔍

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

AspectList<? extends Number>List<Number>List<T extends Number>
Accepts List<Integer>YesNoOnly when T=Integer
Accepts List<Double>YesNoOnly when T=Double
Read element typeNumberNumberT (the specific subtype)
add() allowedNo (except null)YesYes
Return type can reference element typeNo (type is anonymous)YesYes, as T
Named type variable availableNoN/AYes, T
Typical useRead-only processing of any numeric collectionOnly when list truly holds Number instancesWhen 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 NumberintValue(), 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