Wildcards
Unbounded wildcard, upper bounded, lower bounded and PECS principle
Unbounded, upper-bounded, and lower-bounded wildcards — plus the PECS rule that makes real generic APIs practical.
1. Definition
What is it?
A wildcard represents an unknown generic type.
Instead of saying “this is exactly a List<String>,” a wildcard lets you say “this is some kind of List, but I do not know the exact element type here.”
The most common forms are:
<?><? extends T><? super T>
Wildcards are essential because Java generics is invariant.
That means List<Integer> is not a subtype of List<Number> even though Integer is a subtype of Number.
Why does it exist?
Without wildcards, many reusable APIs would be either too restrictive or unsafe.
For example, a method that only wants to read values from a list of numbers should not require exactly List<Number>.
It should also accept List<Integer>, List<Double>, and other numeric lists.
Wildcards express that relationship safely.
Where does it fit?
Wildcards sits between generic basics and type erasure in terms of conceptual difficulty.
It matters heavily in:
- library API design
- collection utilities
- stream and comparator helpers
- framework extension points
- interview questions about variance and substitutability
If generics basics is about parameterizing a type, wildcards is about how different parameterized types cooperate.
2. Core Concepts
2.1 Invariance first
2.1.1 Keywords and contracts you should make explicit here
Wildcards become clearer if you deliberately name the contract words behind them:
invariance—List<Integer>is not a subtype ofList<Number>.unbounded wildcard—<?>, meaning an unknown type with mainly read-as-Objectsemantics.upper-bounded wildcard—<? extends T>, used when values are produced for reading.lower-bounded wildcard—<? super T>, used when values are consumed through writes.producer— a parameter you mainly read from.consumer— a parameter you mainly write to.PECS— shorthand forProducer Extends, Consumer Super.wildcard capture— the compiler technique that lets a helper method treat one unknown wildcard as a consistent internal type variable.
These words are part of the API contract. A good signature tells the caller whether the parameter is read-oriented, write-oriented, or both.
Before wildcards, you must understand invariance.
List<Integer> integers = List.of(1, 2, 3);
// List<Number> numbers = integers; // compilation error
Java forbids that assignment because allowing writes through List<Number> would be unsafe.
If it were allowed, you could add a Double into a List<Integer>.
Wildcards gives controlled flexibility without breaking safety.
2.2 Unbounded wildcard `>`
<?> means “some unknown type.”
It is useful when:
- the code only needs the object as a container
- you only call methods that do not depend on the concrete element type
- you mostly read values as
Object
public static void printAll(List<?> values) {
for (Object value : values) {
System.out.println(value);
}
}
You can read from List<?> as Object.
You cannot safely add arbitrary elements to it, because the element type is unknown.
2.3 Upper-bounded wildcard ` extends T>`
This form means “some unknown subtype of T.”
It is ideal when the structure produces values for you.
public static double sum(List<? extends Number> values) {
double total = 0;
for (Number value : values) {
total += value.doubleValue();
}
return total;
}
This method can accept:
List<Integer>List<Long>List<Double>List<Number>
The key restriction is write safety.
You usually cannot add values to a List<? extends Number> because the actual subtype is unknown.
2.4 Lower-bounded wildcard ` super T>`
This form means “some unknown supertype of T.”
It is ideal when the structure consumes values from you.
public static void addDefaults(List<? super Integer> target) {
target.add(0);
target.add(1);
}
This method can accept:
List<Integer>List<Number>List<Object>
You can safely add Integer values.
But when reading back, the compiler can only promise Object, because the precise stored type is not known.
2.5 PECS
PECS stands for:
- Producer Extends
- Consumer Super
It is a memory aid, not a formal language rule.
Use extends when:
- you mainly read values
- the source structure produces values for your algorithm
Use super when:
- you mainly write values
- the target structure consumes values from your algorithm
This is probably the single most important wildcard interview rule.
2.6 Wildcard capture
Sometimes a wildcard API is correct externally, but the implementation needs a concrete type parameter internally.
That is when helper methods with their own type parameter become useful.
public static void swapFirstTwo(List<?> list) {
swapHelper(list);
}
private static <T> void swapHelper(List<T> list) {
T first = list.get(0);
list.set(0, list.get(1));
list.set(1, first);
}
This technique is called wildcard capture.
The helper method lets the compiler “capture” the unknown wildcard as one consistent type.
2.7 Why `List
List<Object> is a concrete parameterization.
It does not mean “list of anything.”
It means “list that accepts objects of any subtype of Object through this exact List<Object> view.”
That is different from List<?>, which means a list of one unknown element type.
This distinction eliminates a lot of confusion.
3. Practical Usage
Read-only collection APIs
If your method only reads from a collection, prefer ? extends over an exact type.
Examples:
- statistics over numeric values
- formatting or logging entries
- validating elements
- copying from a source collection
This makes the API more reusable for callers.
Write-focused APIs
If your method writes values into a target structure, prefer ? super.
Examples:
- batch insertion helpers
- copying mapped results into a caller-provided collection
- event dispatch registration into broader handler buckets
This makes intent explicit: the method is a producer of T values.
The copy pattern
The classic example combines both sides:
public static <T> void copy(List<? extends T> source, List<? super T> target) {
for (T item : source) {
target.add(item);
}
}
This one method expresses variance better than many paragraphs.
It also demonstrates why PECS is useful in real life.
Framework and library usage
You often see wildcards in:
Comparator<? super T>Class<? extends Annotation>Consumer<? super T>Supplier<? extends T>Stream<? extends T>-style helper signatures
A strong interview answer should mention at least one real JDK example.
Choosing between wildcard and type parameter
Use a wildcard when the method does not need to name the exact type.
Use a named type parameter when the method must connect multiple positions with the same type.
For example:
List<?>is enough if you only print values<T> T first(List<T>)needs a namedTbecause the return type depends on the element type
That design judgment is more important than memorizing syntax.
Practical checklist
Before choosing a wildcard, ask:
- Do I only read, only write, or both?
- Does the caller need flexibility across a family of generic types?
- Do I need one named type parameter across several arguments or the return type?
- Would a wildcard make the API clearer or hide too much intent?
- Is a helper method needed for wildcard capture?
4. Code Examples
Example 1 — Unbounded wildcard
public static void debug(List<?> values) {
for (Object value : values) {
System.out.println(value);
}
}
This is appropriate because the method only needs read access as Object.
Example 2 — Producer with `extends`
public static double average(List<? extends Number> values) {
if (values.isEmpty()) {
throw new IllegalArgumentException("values must not be empty");
}
double total = 0;
for (Number value : values) {
total += value.doubleValue();
}
return total / values.size();
}
The list produces Number-like values for the algorithm.
Example 3 — Consumer with `super`
public static void addIds(List<? super Integer> target) {
target.add(1001);
target.add(1002);
}
The method is a producer of Integer values and the target consumes them.
Example 4 — Copy with PECS
public static <T> void copy(List<? extends T> source, List<? super T> target) {
for (T item : source) {
target.add(item);
}
}
This is the signature most interviewers hope you can explain clearly.
Example 5 — Wildcard capture helper
public static void swapFirstTwo(List<?> list) {
swapHelper(list);
}
private static <T> void swapHelper(List<T> list) {
T first = list.get(0);
list.set(0, list.get(1));
list.set(1, first);
}
Without the helper method, direct mutation is awkward because ? is unknown.
5. Trade-offs
| Aspect | Advantage | Cost / risk |
|---|---|---|
| Flexibility | Accepts wider families of generic inputs | Signatures become harder to read for beginners |
| Safety | Preserves type safety across variance scenarios | Overusing wildcards can hide intent |
| API reuse | Makes utility methods broadly usable | The wrong wildcard (extends vs super) breaks usability |
| Documentation | Shows whether the API reads, writes, or both | Requires mental model of invariance and PECS |
| Maintainability | Good signatures prevent unsafe casts later | Bad signatures cause confusing compiler messages |
The main trade-off is clarity versus flexibility.
A wildcard is good when it expresses a real contract.
It is bad when it is added only because the author wants a “fancy” generic signature.
6. Common Mistakes
1. Thinking `List` is a subtype of `List`
It is not.
That is the root reason wildcards exist.
2. Using `extends` and then trying to add values
A producer with extends is usually read-only from the caller's perspective.
Trying to write into it is the classic beginner mistake.
3. Using `super` and expecting precise read types
From List<? super Integer>, reads are effectively Object unless you cast.
That surprises many developers.
4. Replacing every type parameter with a wildcard
Not every generic signature should use ?.
If multiple arguments or the return type must share one exact type, use a named type parameter instead.
5. Confusing `List
These two express different contracts.
List<Object> is specific.
List<?> is unknown.
6. Forgetting PECS during design
If you do not classify a parameter as producer or consumer, you are likely to choose the wrong bound.
7. Deep Dive
Why Java chose use-site variance
Some languages use declaration-site variance, where the type itself is marked covariant or contravariant.
Java instead uses variance at the use site with wildcards.
That makes some signatures noisier, but keeps generic type declarations simpler and backward-compatible.
Arrays versus generics
Java arrays are covariant.
That means Integer[] is assignable to Number[].
But it is only partially safe and can fail at runtime with ArrayStoreException.
Generics learned from that design compromise.
They choose stricter compile-time safety instead.
This is a strong senior-level comparison to mention in interviews.
Wildcard capture as implementation technique
Good public APIs often expose wildcards.
Internal helper methods often replace them with named type parameters.
That split keeps the public contract flexible while keeping the implementation precise.
Reading compiler intent
When Java says two generic types are incompatible, it is usually protecting a mutation hole.
Instead of fighting the compiler, ask:
- who is producing values?
- who is consuming values?
- does this method really need one shared type variable?
That mindset turns confusing generic errors into design feedback.
Senior interview angle
A senior answer about wildcards does not stop at PECS.
It also explains:
- why invariance exists
- why the wrong bound harms API ergonomics
- how wildcard capture solves implementation friction
- why arrays are a useful comparison point
8. Interview Questions
Why is `List` not a subtype of `List`?
Because if it were, code could add a Double through the List<Number> view and corrupt the original List<Integer>.
When do you use `? extends T`?
When the structure produces values of some subtype of T for your code to read.
When do you use `? super T`?
When the structure consumes T values that your code wants to write into it.
What does PECS mean?
Producer Extends, Consumer Super.
It is a practical memory rule for choosing wildcard bounds.
When is a wildcard worse than a named type parameter?
When the method needs to tie multiple positions together with one exact type, such as argument and return type relationships.
9. Glossary
| Term | Meaning |
|---|---|
| invariance | List<A> is not a subtype of List<B> even if A is a subtype of B |
| wildcard | Unknown generic type placeholder such as ? |
| upper bound | ? extends T |
| lower bound | ? super T |
| PECS | Producer Extends, Consumer Super |
| wildcard capture | Converting an unknown wildcard into a named type variable via helper method |
| producer | Structure you mainly read values from |
| consumer | Structure you mainly write values into |
10. Cheatsheet
- Start with invariance; wildcards only make sense after that.
<?>means unknown type, mostly read-only asObject.<? extends T>is for producers.<? super T>is for consumers.- PECS is the fastest interview heuristic.
List<Object>is not the same asList<?>.- Use a named type parameter when argument and return types must stay linked.
- Use wildcard capture helper methods for internal mutation logic.
- Compare wildcards to array covariance for a strong senior explanation.
- Choose the bound that matches the API contract, not the one that “looks advanced.”
🎮 Games
10 questions