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.

// File: WhyUpperBoundedDemo.java import java.util.List; public class WhyUpperBoundedDemo { // ATTEMPT 1: accept List<Number> directly // Problem: List<Integer> is NOT a List<Number> (invariance) // A caller with List<Integer> cannot use this method static double sumV1(List<Number> numbers) { double total = 0; for (Number n : numbers) { total += n.doubleValue(); } return total; } // ATTEMPT 2: accept List<?> - the unbounded wildcard // Problem: elements come out as Object - cannot call doubleValue() static double sumV2(List<?> items) { double total = 0; for (Object item : items) { // total += item.doubleValue(); // COMPILE ERROR - Object has no doubleValue() // Would need a cast - losing compile-time safety } return total; } // CORRECT: accept List<? extends Number> // Reads any Number subtype, calls Number methods safely static double sum(List<? extends Number> numbers) { double total = 0; for (Number n : numbers) { // elements come out as Number - not Object total += n.doubleValue(); // Number method - available without cast } return total; } public static void main(String[] args) { List<Integer> intAmounts = List.of(499, 1299, 799); List<Double> doubleAmounts = List.of(499.0, 1299.0, 799.0); List<Long> longAmounts = List.of(499L, 1299L, 799L); System.out.println("=== sum(List<? extends Number>) accepts all numeric lists ==="); System.out.println("Integer list sum: Rs." + sum(intAmounts)); System.out.println("Double list sum: Rs." + sum(doubleAmounts)); System.out.println("Long list sum: Rs." + sum(longAmounts)); System.out.println(); System.out.println("=== List<Number> approach fails at compile time ==="); // sumV1(intAmounts); // COMPILE ERROR // "incompatible types: List<Integer> cannot be converted to List<Number>" // sumV1(doubleAmounts); // COMPILE ERROR - same invariance issue System.out.println("List<Number> only works for actual List<Number> - rarely created"); System.out.println(); System.out.println("=== add() is disallowed on List<? extends Number> ==="); List<Integer> mutableInts = new java.util.ArrayList<>(List.of(10, 20, 30)); List<? extends Number> readableList = mutableInts; Number first = readableList.get(0); // reading is fine - comes out as Number System.out.println("Read from wildcard: " + first); // readableList.add(40); // COMPILE ERROR // readableList.add(3.14); // COMPILE ERROR // The compiler rejects any add() because it cannot verify the subtype System.out.println("add() rejected by compiler - read-only contract enforced"); } }
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.

// File: UpperBoundReadingDemo.java import java.util.List; public class UpperBoundReadingDemo { // Statistics methods that work for any numeric list static double average(List<? extends Number> values) { if (values.isEmpty()) return 0.0; double sum = 0; for (Number value : values) { sum += value.doubleValue(); // Number method - no cast } return sum / values.size(); } static double max(List<? extends Number> values) { if (values.isEmpty()) throw new java.util.NoSuchElementException(); double result = values.get(0).doubleValue(); for (Number value : values) { double d = value.doubleValue(); if (d > result) result = d; } return result; } static double min(List<? extends Number> values) { if (values.isEmpty()) throw new java.util.NoSuchElementException(); double result = values.get(0).doubleValue(); for (Number value : values) { double d = value.doubleValue(); if (d < result) result = d; } return result; } // Works with ? extends Comparable<T> - for any type that can compare itself static <T extends Comparable<T>> T maxComparable(List<? extends T> items) { if (items.isEmpty()) throw new java.util.NoSuchElementException(); T result = items.get(0); // T - not Object for (T item : items) { if (item.compareTo(result) > 0) result = item; } return result; // returns T - caller gets the actual type } public static void main(String[] args) { List<Integer> scores = List.of(72, 88, 95, 64, 91); List<Double> ratings = List.of(4.2, 3.8, 4.9, 4.1, 3.5); List<Long> revenue = List.of(450000L, 720000L, 380000L, 910000L); System.out.println("=== average() with different numeric types ==="); System.out.printf("Average score : %.2f%n", average(scores)); System.out.printf("Average rating : %.2f%n", average(ratings)); System.out.printf("Average revenue: Rs.%.2f%n", average(revenue)); System.out.println(); System.out.println("=== max() and min() ==="); System.out.println("Highest score : " + max(scores)); System.out.println("Lowest rating : " + min(ratings)); System.out.println(); System.out.println("=== maxComparable() - works for any Comparable type ==="); List<String> cities = List.of("Bengaluru", "Mumbai", "Delhi", "Hyderabad"); String lastCity = maxComparable(cities); // T inferred as String System.out.println("Last city (lexicographically): " + lastCity); // Notice: maxComparable returns T (String here) - not Object // The caller gets the actual type without any cast } }
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
// File: PecsProducerDemo.java import java.util.List; import java.util.ArrayList; public class PecsProducerDemo { // PRODUCER: src PRODUCES elements (we read from it) // src is the producer -> ? extends T // dest is the consumer -> ? super T (lower-bounded - consumer side) static <T> void copyAll(List<? super T> dest, List<? extends T> src) { for (T element : src) { // reading from producer (? extends T) -> T dest.add(element); // writing to consumer (? super T) -> accepts T } } // All these work as PRODUCERS (read-only access): static double computeTotal(List<? extends Number> amounts) { return amounts.stream().mapToDouble(Number::doubleValue).sum(); } // A method that shows BOTH sides - reading from one, writing to another static List<Double> toDoubles(List<? extends Number> source) { List<Double> result = new ArrayList<>(); for (Number n : source) { // source PRODUCES Number values result.add(n.doubleValue()); // result is our own List<Double> - we control it } return result; } public static void main(String[] args) { System.out.println("=== copyAll - Producer (? extends) + Consumer (? super) ==="); List<Integer> intSource = List.of(499, 1299, 799); List<Number> numDest = new ArrayList<>(); copyAll(numDest, intSource); // dest=List<Number>, src=List<Integer> System.out.println("Copied to List<Number>: " + numDest); List<Object> objDest = new ArrayList<>(); copyAll(objDest, intSource); // dest=List<Object>, src=List<Integer> System.out.println("Copied to List<Object>: " + objDest); System.out.println(); System.out.println("=== computeTotal - producer reading all numeric types ==="); List<Double> orderAmounts = List.of(799.0, 1299.0, 499.0, 2499.0); List<Integer> itemCounts = List.of(3, 1, 5, 2); System.out.printf("Order total : Rs.%.2f%n", computeTotal(orderAmounts)); System.out.printf("Item total : %.2f%n", computeTotal(itemCounts)); System.out.println(); System.out.println("=== toDoubles - converting any numeric list to List<Double> ==="); List<Integer> intPrices = List.of(499, 1299, 799); List<Double> asDoubles = toDoubles(intPrices); System.out.println("Integer prices as Doubles: " + asDoubles); } }
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.

// File: NonListWildcardDemo.java import java.util.*; public class NonListWildcardDemo { // Works with any Map whose values are Number subtypes static double sumMapValues(Map<String, ? extends Number> map) { double total = 0; for (Number value : map.values()) { // values() returns Collection<? extends Number> total += value.doubleValue(); } return total; } // Works with Optional holding any Number subtype static double getValueOrZero(Optional<? extends Number> opt) { return opt.map(Number::doubleValue).orElse(0.0); } // Works with any Iterable of Comparable elements static <T extends Comparable<T>> T findMin(Iterable<? extends T> items) { T min = null; for (T item : items) { if (min == null || item.compareTo(min) < 0) min = item; } if (min == null) throw new NoSuchElementException("Iterable is empty"); return min; } public static void main(String[] args) { System.out.println("=== sumMapValues - Map<String, ? extends Number> ==="); Map<String, Integer> intMap = new LinkedHashMap<>(); intMap.put("Electronics", 42500); intMap.put("Clothing", 18300); intMap.put("Home", 11200); Map<String, Double> doubleMap = new LinkedHashMap<>(); doubleMap.put("Q1", 125000.0); doubleMap.put("Q2", 148000.0); doubleMap.put("Q3", 132000.0); System.out.printf("Integer map total: Rs.%.0f%n", sumMapValues(intMap)); System.out.printf("Double map total : Rs.%.0f%n", sumMapValues(doubleMap)); System.out.println(); System.out.println("=== getValueOrZero - Optional<? extends Number> ==="); Optional<Integer> presentInt = Optional.of(2499); Optional<Double> presentDouble = Optional.of(3.14); Optional<Number> empty = Optional.empty(); System.out.println("Integer Optional: " + getValueOrZero(presentInt)); System.out.println("Double Optional : " + getValueOrZero(presentDouble)); System.out.println("Empty Optional : " + getValueOrZero(empty)); System.out.println(); System.out.println("=== findMin - Iterable<? extends Comparable> ==="); // Works with TreeSet, LinkedList, or any Iterable TreeSet<Integer> sortedScores = new TreeSet<>(List.of(72, 88, 95, 64, 91)); System.out.println("Min score: " + findMin(sortedScores)); List<String> cities = List.of("Mumbai", "Bengaluru", "Chennai", "Ahmedabad"); System.out.println("First city: " + findMin(cities)); } }
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

Basic Example

A simple method that sums any list of numbers — List<Integer>, List<Double>, or any other Number subtype — using List<? extends Number>.

import java.util.List; public class Main { static double sum(List<? extends Number> numbers) { double total = 0; for (Number n : numbers) { total += n.doubleValue(); } return total; } public static void main(String[] args) { List<Integer> scores = List.of(10, 20, 30); List<Double> prices = List.of(9.5, 4.5, 2.0); System.out.println("Score total: " + sum(scores)); System.out.println("Price total: " + sum(prices)); } }

Output

Score total: 60.0
Price total: 16.0

How it works:

  1. ›List<? extends Number> means "a list of Number or any of its subtypes," so sum accepts List<Integer> and List<Double> alike.
  2. ›Each element read from the list comes out typed as Number, so n.doubleValue() can be called without any cast.
  3. ›The list cannot be written to through this parameter — ? extends Number only grants read access, which is exactly what sum needs.

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>

import java.util.List; import java.util.ArrayList; // WRONG - the compiler rejects every add() call on List<? extends Number> // because the actual list type is unknown. Adding 42 to a List<Double> // or adding 3.14 to a List<Integer> would corrupt the list. static void tryToAdd(List<? extends Number> numbers) { // numbers.add(42); // COMPILE ERROR // numbers.add(3.14); // COMPILE ERROR // numbers.add(null); // compiles - null is always safe, but adds nothing useful } // CORRECT - if elements need to be added, use a named type parameter // The named type parameter <T extends Number> allows both reading and writing static <T extends Number> void addAndRead(List<T> numbers, T newValue) { numbers.add(newValue); // legal - T is known at the call site for (T n : numbers) { System.out.println(n.doubleValue()); // Number method available via bound } }

Mistake 2 - Confusing ? extends T With T in the Return Type

import java.util.List; // WRONG ASSUMPTION - a developer might try to return the element type // from a method declared with ? extends Number. The ? has no name, // so it cannot appear in the return type. static ? extends Number getFirst(List<? extends Number> numbers) { // COMPILE ERROR: "illegal start of type" - ? cannot be a return type return numbers.get(0); } // CORRECT OPTION A - return Number (the bound) when the specific subtype is irrelevant static Number getFirstAsNumber(List<? extends Number> numbers) { return numbers.get(0); // returns Number - caller gets Number reference } // CORRECT OPTION B - use a named type parameter when the exact type matters // This lets the caller know the return type matches the list's element type static <T extends Number> T getFirstTyped(List<T> numbers) { return numbers.get(0); // returns T - for List<Integer> returns Integer }

Mistake 3 - Using ? extends T When the Subtype Must Be Preserved

import java.util.ArrayList; import java.util.List; // WRONG - transforming a List<Integer> into a List<Integer> using ? // The wildcard loses the element type information in the return path static List<? extends Number> doubleAll(List<? extends Number> numbers) { List<Number> result = new ArrayList<>(); // cannot use List<T> - no T for (Number n : numbers) { result.add(n.doubleValue() * 2); // adds Double to List<Number> } return result; // A caller with List<Integer> gets back List<Number> - the Integer type is lost } // CORRECT - named type parameter preserves the element type through transformation static <T extends Number> List<Double> doubleAllTyped(List<T> numbers) { List<Double> result = new ArrayList<>(); for (T n : numbers) { result.add(n.doubleValue() * 2); // correctly converts to Double } return result; // clearly returns List<Double> regardless of input type }

Mistake 4 - Applying ? extends Where an Unbounded ? Would Do

import java.util.List; // UNNECESSARY - printAll only calls toString() implicitly via println. // toString() is on Object - no Number methods are called. // ? extends Number is over-specified and restricts callers unnecessarily. static void printAllNumbers(List<? extends Number> items) { for (Number item : items) { System.out.println(item); // only toString() is called - available on Object } } // BETTER - use plain ? - accepts any list, communicates "we don't use the type" static void printAll(List<?> items) { for (Object item : items) { System.out.println(item); // Object reference is sufficient for println } } // RESERVE ? extends T for when T-specific methods are actually called static double sumNumbers(List<? extends Number> items) { double total = 0; for (Number item : items) { total += item.doubleValue(); // Number.doubleValue() - ? extends Number JUSTIFIED } return total; }

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