Wildcards in Java Generics
Use ?, ? extends T, and ? super T to master covariance and contravariance in Java method parameters.
Wildcards in Java Generics
Wildcards (?) represent an unknown type in generic usage. They are a way to express read patterns and write patterns at the call site without committing to a specific type argument. The three forms cover most scenarios: unbounded (?), upper-bounded (? extends T), and lower-bounded (? super T).
When to Use Wildcards
-
Read-only parameters — use
? extends Twhen a method only reads from a collection -
Write-only parameters — use
? super Twhen a method only writes to a collection -
Flexible APIs — when a method works with any subtype or supertype of a known type
-
Consumer-Producer pattern — when a parameter both reads and writes, use different bounds for each position (PECS)
When NOT to Use Wildcards
-
Return types — wildcards in return types force callers to deal with unknown types; prefer specific type parameters
-
Class field types — storing a wildcard in a field loses type information permanently
-
Overly complex signatures — chained wildcards like
List<? extends List<? extends Number>>are hard to read and usually a sign of over-engineering
Code Example: Upper-Bounded Wildcard (? extends T)
// Reading: we only need to know elements are at least T
public static double sum(List<? extends Number> numbers) {
double total = 0.0;
for (Number n : numbers) { // safe read — Number is the upper bound
total += n.doubleValue();
}
return total;
}
// Works with any List of a Number subtype
List<Integer> ints = Arrays.asList(1, 2, 3);
List<Double> doubles = Arrays.asList(1.1, 2.2);
sum(ints); // ? extends Number matches Integer
sum(doubles); // ? extends Number matches Double
Code Example: Lower-Bounded Wildcard (? super T)
// Writing: we can accept T or any supertype of T
public static void addNumbers(List<? super Integer> list) {
list.add(1); // safe write — we know list holds at least Integer
list.add(2);
// list.add(1.5); // compile error — list might be List<Number>
}
List<Number> numList = new ArrayList<>();
List<Object> objList = new ArrayList<>();
addNumbers(numList); // ? super Integer matches Number
addNumbers(objList); // ? super Integer matches Object
Code Example: PECS (Producer Extends, Consumer Super)
public static <T> void copy(List<? extends T> source, List<? super T> dest) {
for (T item : source) { // source produces T (we read it)
dest.add(item); // dest consumes T (we write to it)
}
}
List<Number> numbers = new ArrayList<>();
List<Integer> ints = Arrays.asList(1, 2, 3);
copy(ints, numbers); // T = Number, source=? extends Integer, dest=? super Number
Code Example: Unbounded Wildcard (?)
public static boolean isEmpty(List<?> list) {
return list.isEmpty();
// we only call methods common to all List<?> regardless of element type
}
// List<?> means "a List of some unknown type"
// We can read elements as Object (everything is an Object)
public static void printAll(List<?> list) {
for (Object item : list) {
System.out.println(item);
}
// list.add("new"); // compile error — we cannot add anything (we don't know the type)
}
Mermaid Diagram: Wildcard Variance
classDiagram
class Number {
<<Number>>
}
class Integer {
<<Integer>>
}
class Double {
<<Double>>
}
class Object {
<<Object>>
}
Integer --|> Number
Double --|> Number
Number --|> Object
direction TB
note for "? extends Number" as WC1
note "Can READ as Number\nCan NOT WRITE (except null)"
note for "? super Integer" as WC2
note "Can WRITE as Integer\nCan READ as Object"
note for "? super Number" as WC3
note "Can WRITE as Number\nCan READ as Object"
Failure Scenarios
1. Adding Elements to ? extends T
public static void wrongAdd(List<? extends Number> list) {
list.add(Double.valueOf(1.0)); // compile error!
// The compiler only knows list is List<X> where X extends Number.
// It could be List<Integer>, so adding a Double is unsafe.
}
// Fix: if you need to write, use ? super T instead, or do not use wildcards
2. Reading From ? super T
public static void wrongRead(List<? super Integer> list) {
Integer val = list.get(0); // compile error!
// The compiler only knows list is List<X> where X super Integer.
// It could be List<Object>, and Object is not Integer.
Object val = list.get(0); // only safe read is Object
}
3. Mixing Read and Write with the Same Wildcard
public static void mixed(List<? extends Number> list) {
list.add(Integer.valueOf(1)); // compile error
Number n = list.get(0); // fine
}
// ? extends T is read-only; ? super T is write-only
// For mixed access, use the type parameter directly: <T> void process(List<T> list)
Trade-Off Table
| Pattern | Use When | Cannot Do |
| ----------------- | ------------------------------------------- | -------------------------------------------------------------- |
| ? extends T | Reading elements as T | Add non-null elements (type unknown) |
| ? super T | Writing elements of type T | Read as anything more specific than Object |
| ? (unbounded) | Operations that need any type info | Read as anything more specific than Object / add any element |
| <T> (parameter) | Needing both read and write, or return type | More verbose call site |
Observability Checklist
-
Confirm wildcard direction matches the data flow (PECS rule)
-
Check return types — wildcards in return types force casts; avoid unless necessary
-
Verify static analysis flags “Exceeds bounds of wildcard” warnings
-
Ensure API documentation describes what wildcards mean for callers (read-only, write-only)
-
Consider refactoring to
<T>if a method signature has multiple wildcards that confuse callers
Security Notes
-
Wildcards do not provide runtime type safety:
List<? extends Number>andList<Integer>both erase toListat runtime. Untrusted data in collections cannot be protected by wildcards. -
Unsafe cast via wildcard:
List<?> list = new ArrayList<String>();compiles fine, but adding a non-String and retrieving it will throwClassCastException. Wildcards let you bypass compile-time checks at your own risk. -
Reflection and wildcards:
Method.getParameterTypes()returns the raw generic type — wildcards are stripped. Passing wildcard-parameterized collections to reflective methods requires extra care.
Pitfalls
-
?vsObjectin Lists:List<?>andList<Object>are not the same.List<?>has unknown element type so you cannot add anything;List<Object>accepts anything. -
Capture confusion: When a wildcard is captured by inference, you may get compiler messages like “capture#1 of ?” — this happens when the compiler infers a specific type for the wildcard but cannot express it.
-
No nested wildcards for collections:
List<List<? extends Number>>is valid but hard to use — you can read inner lists but not add elements to them.
Quick Recap
-
? extends T— producer pattern: read elements asT, cannot add elements (exceptnull) -
? super T— consumer pattern: write elements asT, read asObject -
?(unbounded) — unknown type, limited toObjectreads and no additions -
PECS:
? extends Tfor producers (input to your method),? super Tfor consumers (output from your method) -
Wildcards are for call-site flexibility, not storage; return types should use concrete type parameters
-
Type erasure makes wildcards compile-time only — no runtime type information
Key Takeaways
Wildcards solve the asymmetry between invariant generic types and the covariant/contravariant relationships you actually need at call sites. List<String> is not a subtype of List<Object> — generics are invariant by default — but you often want to pass a List<Integer> where a List<? extends Number> is expected. Wildcards make this work without forcing you to abandon type safety.
The PECS rule (Producer Extends, Consumer Super) is the practical heuristic: if your method only reads from a collection, use ? extends T — the compiler lets you treat elements as T. If your method only writes to a collection, use ? super T — the compiler lets you pass elements of type T. Trying to do both with the same wildcard is a compile error for good reason.
The critical insight is that wildcards are for call-site flexibility, not storage. Storing a wildcard in a field permanently discards type information. Return types should never use wildcards — forcing callers to deal with an unknown type is poor API design.
The bounded wildcard ? extends T lets you read as T but not write (except null), because the compiler cannot guarantee the collection’s actual element type. ? super T lets you write as T but only read as Object, because the collection might hold supertypes of T. These are not flaws — they are the type system enforcing that you cannot violate the collection’s element type contract.
For how all this works under the hood, see Type Erasure in Java Generics — wildcards are compile-time constructs that vanish at runtime just like all other generic syntax.
Interview Questions
::: info
These questions use the .qa-card CSS class structure. Each card has a .qa-question and .qa-answer div.
:::
Model Answer: "`? extends T` is an upper-bounded wildcard — it represents some unknown subtype of `T`. You can **read** elements as `T` but cannot reliably **write** to it (the unknown subtype could reject the value). `? super T` is a lower-bounded wildcard — it represents some unknown supertype of `T`. You can **write** elements of type `T` into it but can only safely **read** as `Object`."
Model Answer: "PECS stands for **Producer Extends, Consumer Super**. When a method parameter is a collection that your method only reads from (produces elements), use `? extends T`. When your method only writes to it (consumes elements), use `? super T`. When your method does both, use the concrete type parameter `
Model Answer: "Because `List` could legally be a `List
Category
Related Posts
Abstract Classes in Java
Learn about partially implemented classes that define contracts for subclasses using abstract methods and concrete implementations.
Arithmetic Operators in Java
Master Java arithmetic operators: addition, subtraction, multiplication, division, and modulo with integer division gotchas and operator precedence explained.
Array Basics in Java
Learn Java array fundamentals: declaration, initialization, element access, and the length property explained simply.