Java Bounded Wildcards: Syntax and Use Cases
java bounded wildcard: Learn how Java bounded wildcards (? extends T and ? super T) control type flexibility in generic code while preserving compile-time safety.
Java generics are invariant. A List<String> is not a List<Object>, even though String is an Object. That invariance keeps assignments type-safe, but it also makes generic code rigid. If a method accepts List<Object>, you cannot pass a List<String> to it, even when the method only reads elements. The java bounded wildcard feature relaxes that rigidity within a controlled boundary.
The Problem Bounded Wildcards Solve
The syntax ? extends T declares an upper bound: the unknown type is T or any subtype of T. The syntax ? super T declares a lower bound: the unknown type is T or any supertype of T. These two forms let a method accept collections of related element types without sacrificing the compiler's ability to check assignments.
Without wildcards, a method that processes numbers must either declare a concrete type such as List<Integer>, which excludes List<Double>, or fall back to a raw Collection, which removes all element type checking. Bounded wildcards give a middle position: the method knows a bound for every element, even though it does not know the exact element type.
Upper Bounded Wildcards: ? extends T
An upper bounded wildcard allows a method to accept a collection of any type that is a subtype of a given bound. A classic example is a method that sums numbers:
public static double sum(Collection<? extends Number> numbers) { double total = 0.0; for (Number n : numbers) { total += n.doubleValue(); } return total; }
This method accepts List<Integer>, List<Double>, and List<BigDecimal>, because each of those element types is a subtype of Number. Without the wildcard, you would need one overload per numeric type.
The tradeoff is that you cannot add elements to a Collection<? extends Number>. The compiler does not know the exact element type; it only knows that every element is some subtype of Number. Adding a Number instance is unsafe because the actual collection might be a List<Integer>. Reads are safe because every element can be assigned to Number.
Lower Bounded Wildcards: ? super T
A lower bounded wildcard does the opposite. It accepts a collection whose element type is T or any supertype of T. This is useful when a method writes elements into a collection.
public static void addIntegers(Collection<? super Integer> sink) { sink.add(1); sink.add(2); sink.add(3); }
The method can write Integer values into a List<Integer>, a List<Number>, or a List<Object>, because Integer is assignable to each of those element types. Reads, however, are restricted. You can only read elements as Object, because the actual element type could be Number or Object, and the compiler cannot know which one.
The Get and Put Principle
The practical rule that ties both forms together is often called the Get and Put Principle, or PECS (Producer Extends, Consumer Super). If a method only produces values from a collection, use ? extends T. If a method only consumes values by adding them to a collection, use ? super T. If a method both reads and writes, a bounded wildcard is the wrong tool; use a concrete type parameter instead.
Consider a copy method:
public static <T> void copy(List<? extends T> source, List<? super T> target) { for (T item : source) { target.add(item); } }
The source list produces T values, so it is bounded with extends. The target list consumes T values, so it is bounded with super. This single method copies between List<Integer> and List<Number>, or between List<String> and List<Object>, without requiring the two lists to have identical element types.
Compile-Time Behavior and Common Mistakes
Bounded wildcards are a compile-time construct. They do not change the runtime representation of a generic type; the JVM erases wildcards just as it erases type parameters. The safety they provide exists entirely in the compiler.
A frequent mistake is treating ? extends T as a writable collection. The following does not compile:
List<? extends Number> numbers = new ArrayList<Integer>(); numbers.add(3.14); // error: cannot add Double to List<? extends Number>
The compiler rejects the call because the actual list could be a List<Integer>. The same error appears in reverse form when reading from a ? super T collection. Understanding which operations each bound permits prevents these errors before they reach production.
When Bounded Wildcards Hurt Maintainability
Bounded wildcards add flexibility, but they also add complexity. A signature like Map<String, ? extends Collection<? extends Number>> is difficult to read and harder to reason about than a method that uses a named type parameter. If the wildcard appears only once in a signature, or if callers rarely pass heterogeneous types, a concrete type parameter is usually clearer.
A named type parameter also lets a method relate multiple arguments. A wildcard cannot express that the source and target of a copy operation share the same element type. When that relationship matters, prefer a generic method with an explicit <T> over a wildcard signature. The wildcard is the right choice when the method does not care about the exact type and only needs a bound to work with.
Choosing Between Wildcards and Type Parameters
The decision between a bounded wildcard and a type parameter depends on how the method uses the type. If the type appears once and is used only for reading or only for writing, a wildcard keeps the signature concise. If the type appears multiple times, or if the method needs to return a value of that type, a type parameter expresses the relationship explicitly.
// Wildcard: concise for a single read-only use public static int countGreaterThan(List<? extends Number> values, double threshold) { int count = 0; for (Number value : values) { if (value.doubleValue() > threshold) { count++; } } return count; }
The same method written with a type parameter is more verbose but offers no additional safety because the type is never used in a second position. Reserve type parameters for signatures where the type connects two or more parts of the method, such as a method that both reads from one collection and writes to another. In that case, the wildcard cannot express the shared type relationship, and the explicit parameter becomes the clearer, safer option.