Question
Java PECS: Understanding Producer Extends and Consumer Super
Question
I encountered PECS, short for “Producer Extends, Consumer Super,” while learning about Java generics. How should I use PECS to decide whether a wildcard type should use extends or super, and how does it resolve the difference between them?
Short Answer
PECS is a practical rule for choosing Java generic wildcards. By the end, you will know why ? extends T is best when code reads T values from a structure, why ? super T is best when code writes T values into one, and why Java collections are invariant by default.
Concept
Java generics are invariant. Even though Integer is a subtype of Number, a List<Integer> is not a subtype of List<Number>.
// This does not compile:
// List<Number> numbers = new ArrayList<Integer>();
If this were allowed, code could add a Double to numbers, but the actual list only accepts Integer values. Java prevents that unsafe situation.
Wildcards let an API accept a range of related generic types safely:
? extends Tmeans “an unknown type that isTor a subtype ofT.”? super Tmeans “an unknown type that isTor a supertype ofT.”
PECS helps choose between them:
- Producer Extends: use
? extends Twhen your code gets or readsTvalues from the parameter.
Mental Model
Imagine containers with unknown but related labels.
A List<? extends Number> is a box labelled “some specific kind of number.” It might be an Integer box or a Double box. You can safely take out a Number, because every possible item is at least a Number. But you cannot safely put a number in: if it is really an Integer box, adding a Double would be wrong.
A List<? super Integer> is a box labelled “Integer or something broader.” It might be a List<Integer>, List<Number>, or List<Object>. You can safely put in an Integer, because all those boxes allow integers. But when taking an item out, Java can only promise it is an Object.
A useful memory aid is:
Get with
extends; put withsuper.
This is a guideline for a method parameter's main job, not a claim that no other operation is possible.
Syntax and Examples
extends is usually used for safe reading:
import java.util.List;
static double total(List<? extends Number> values) {
double sum = 0;
for (Number value : values) {
sum += value.doubleValue();
}
return sum;
}
This method accepts List<Integer>, List<Double>, and List<Number>:
List<Integer> scores = List.of(10, 20, 30);
List<Double> prices = List.of(1.50, 2.75);
System.out.println(total(scores)); // 60.0
System.out.println(total(prices)); // 4.25
Although you can read Number values, you cannot add ordinary values:
List<? extends Number> values = List.of(1, 2, 3);
Step by Step Execution
Consider a type-safe copy method:
import java.util.ArrayList;
import java.util.List;
static <T> void copyAll(List<? extends T> source, List<? super T> destination) {
for (T item : source) {
destination.add(item);
}
}
List<Integer> source = List.of(3, 5, 8);
List<Number> destination = new ArrayList<>();
copyAll(source, destination);
System.out.println(destination); // [3, 5, 8]
Step by step:
- Java infers
TasIntegerfor this call. sourcehas typeList<? extends Integer>. AList<Integer>is valid, and each item can be read as anInteger.destinationhas typeList<? super Integer>. AList<Number>is valid becauseNumberis a supertype ofInteger.
Real World Use Cases
- Calculating statistics: Accept
List<? extends Number>when summing, averaging, or finding the largest numeric value. - Copying data: Read from a specific subtype and write into a list of that type or a broader type.
- Callback registration: A method that sends
Eventobjects to handlers can accept consumers able to handleEventor a broader type. - Sorting: Java's
Collections.sortacceptsComparator<? super T>. A comparator forObjectcan compareStringvalues because it can consume anyStringas anObject. - Data pipelines: A process may read
Dogrecords from aList<Dog>asAnimalvalues, then write them to aList<Animal>orList<Object>. - Library APIs: A library can make methods work with more caller types without sacrificing compile-time safety.
Real Codebase Usage
In production Java code, PECS most often appears in method parameters, especially reusable utility and library APIs. Avoid putting wildcard types in local variable declarations unless there is a clear reason; concrete types are usually easier to work with locally.
Read-only input parameters
Use extends for input that the method only needs to inspect:
static void printNames(List<? extends CharSequence> names) {
for (CharSequence name : names) {
System.out.println(name);
}
}
This works for List<String>, List<StringBuilder>, and other CharSequence subtypes.
Output destination parameters
Use super when the method places values into a caller-provided collection:
static void addErrors(List<? super String> messages) {
messages.add("Missing email");
messages.add("Password is too short");
}
Functional interfaces
PECS also appears in standard functional APIs. For example:
Common Mistakes
Treating extends as an inheritance declaration
In List<? extends Number>, extends does not create a subclass. It describes an unknown element type bounded by Number.
Adding to an extends collection
List<? extends Number> values = new ArrayList<Integer>();
// values.add(10); // Does not compile
Even an Integer is rejected because the list could actually be a List<Double>. You may add null, but that is rarely useful and can create later problems.
Assuming super reads as the lower bound
List<? super Integer> values = new ArrayList<Number>();
values.add(10);
Object value = values.get(0); // Correct
// Integer score = values.get(0); // Does not compile
Comparisons
| Type | Can read safely as | Can add safely | Typical role |
|---|---|---|---|
List<T> | T | T | Read and write an exact element type |
List<? extends T> | T | Nothing except null | Producer of T values |
List<? super T> | Object | T and subtypes of T | Consumer of T values |
Cheat Sheet
- Java generic types are invariant:
List<Integer>is not aList<Number>. ? extends Tmeans an unknown subtype ofT.- Read values as
T. - Do not add ordinary values.
- Use it for a producer.
- Read values as
? super Tmeans an unknown supertype ofT.- Add
Tvalues safely. - Read values only as
Object. - Use it for a consumer.
- Add
- Memory rule: Get = extends; Put = super.
- Use
List<T>when the method needs an exact element type for both reading and writing. - Use
<T>when types in different parameters or the return value must be linked.
static <T> void copyAll(List<? extends T> source, List<? super T> destination) {
for (T item : source) {
destination.add(item);
}
}
FAQ
What does PECS mean in Java?
PECS means Producer Extends, Consumer Super. It is a rule for selecting bounded wildcards in generic method parameters.
Why can I read from ? extends T but not add to it?
The actual element subtype is unknown. It could be any subtype of T, so adding a particular value might violate that list's real element type.
Why can I add to ? super T but only read Object?
The list may be a List<T> or a list of any broader type. Every such list can accept T, but its existing items are only guaranteed to be Object.
Can I add null to List<? extends T>?
Yes. null is valid for reference types, but adding it is usually not useful and may lead to null-related bugs.
Should every generic parameter use a wildcard?
No. Use wildcards when you need variance and flexibility. Use an exact generic type or a type parameter when your method needs a precise type relationship.
Does PECS apply only to List?
No. It applies to generic types generally, including Collection, , , , and custom generic classes.
Mini Project
Description
Build a small numeric transfer utility. It reads numeric values from lists with different element types and appends them to a destination list safely. This models data-import and aggregation code, where input sources and output storage often use related but different types.
Goal
Create methods that calculate a numeric total and copy values from a subtype list into a compatible destination list using PECS.
Requirements
Requirement 1
Keep learning
Related questions
Add External JAR Files to an IntelliJ IDEA Java Project
Learn how to add external JAR dependencies to an IntelliJ IDEA Java project using module libraries, and when to use Maven or Gradle instead.
Avoiding Java Code in JSP with JSP 2: EL and JSTL Explained
Learn how to avoid Java scriptlets in JSP 2 using Expression Language and JSTL, with examples, best practices, and common mistakes.
Call a Method After a Delay in Android Java
Learn how to run Java code after a delay in Android using Handler.postDelayed, manage the main thread, and cancel callbacks safely.