Question

Java PECS: Understanding Producer Extends and Consumer Super

javagenericssuperbounded-wildcardpecs

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 T means “an unknown type that is T or a subtype of T.”
  • ? super T means “an unknown type that is T or a supertype of T.”

PECS helps choose between them:

  • Producer Extends: use ? extends T when your code gets or reads T values from the parameter.
  • Consumer Super: use ? super T when your code puts or writes T values into the parameter.

The terms describe what the collection does for your method:

  • A list that supplies values to your method is a producer.
  • A list that receives values from your method is a consumer.

This matters when designing flexible, type-safe APIs. For example, a method that copies numbers should accept values from List<Integer>, List<Double>, and List<Number>, then place them into a destination such as List<Number> or List<Object>.

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 with super.

This is a guideline for a method parameter's main job, not a claim that no other operation is possible.

Take Quiz

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);

// values.add(4);       // Does not compile
// values.add(1.5);     // Does not compile

The exact subtype is unknown to the compiler.

super is usually used for safe writing:

import java.util.ArrayList;
import java.util.List;

static void addDefaultScores(List<? super Integer> destination) {
    destination.add(100);
    destination.add(80);
}

List<Number> numbers = new ArrayList<>();
addDefaultScores(numbers);

List<Object> objects = new ArrayList<>();
addDefaultScores(objects);

The method can add Integer values to either destination. When reading from destination, however, the safe type is only Object:

Object item = destination.get(0);
// Integer score = destination.get(0); // Does not compile

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:

  1. Java infers T as Integer for this call.
  2. source has type List<? extends Integer>. A List<Integer> is valid, and each item can be read as an Integer.
  3. destination has type List<? super Integer>. A List<Number> is valid because Number is a supertype of Integer.
  4. The loop reads 3, then 5, then 8 from source as Integer values.
  5. Each Integer is safely added to destination.

The signature also allows copying from List<Integer> into List<Object>. It does not allow copying a List<Number> into a List<Integer>, because a Number could be a Double.

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 Event objects to handlers can accept consumers able to handle Event or a broader type.
  • Sorting: Java's Collections.sort accepts Comparator<? super T>. A comparator for Object can compare String values because it can consume any String as an Object.
  • Data pipelines: A process may read Dog records from a List<Dog> as Animal values, then write them to a List<Animal> or List<Object>.
  • Library APIs: A library can make methods work with more caller types without sacrificing compile-time safety.
Take Quiz

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:

import java.util.Comparator;

static <T> T laterOf(T first, T second, Comparator<? super T> comparator) {
    return comparator.compare(first, second) >= 0 ? first : second;
}

The comparator consumes T values, so ? super T makes the API more flexible.

When not to use a wildcard

If a parameter must both accept and return the same exact type, use a type parameter instead:

static <T> T firstOrDefault(List<T> values, T defaultValue) {
    return values.isEmpty() ? defaultValue : values.get(0);
}

Here, T connects the list element type to the default value and return type. A wildcard cannot express that relationship as clearly.

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

The actual list might be List<Object>, so Java cannot guarantee an item read from it is an Integer.

Using List<Number> when flexibility is needed

static void printTotal(List<Number> values) {
    // ...
}

// printTotal(List.of(1, 2, 3)); // A List<Integer> is not accepted

If the method only reads numbers, use List<? extends Number>.

Applying PECS mechanically

PECS is a design aid, not a substitute for understanding the method. If the method must preserve a relationship between several arguments or returns, introduce a type parameter such as <T>.

Confusing arrays with generic collections

Java arrays are covariant (Integer[] can be assigned to Number[]) but may fail at runtime with ArrayStoreException. Generic collections are invariant and catch comparable mistakes at compile time.

Comparisons

TypeCan read safely asCan add safelyTypical role
List<T>TTRead and write an exact element type
List<? extends T>TNothing except nullProducer of T values
List<? super T>ObjectT and subtypes of TConsumer of T values

extends versus super

List<? extends Number> producer = List.of(1, 2, 3);
Number number = producer.get(0); // Safe

List<? super Integer> consumer = new ArrayList<Number>();
consumer.add(42);                // Safe
Object item = consumer.get(0);   // Safe type is Object

Wildcard versus type parameter

UseBest choiceReason
Method only reads related values? extends TAccepts subtype collections
Method only writes T values? super TAccepts supertype collections
Multiple values must have one matching type<T>Connects types across parameters and return values

For example, use <T> when the same type must flow through the method:

static <T> T chooseFirst(T first, T second) {
    return first;
}
Take Quiz

Cheat Sheet

  • Java generic types are invariant: List<Integer> is not a List<Number>.
  • ? extends T means an unknown subtype of T.
    • Read values as T.
    • Do not add ordinary values.
    • Use it for a producer.
  • ? super T means an unknown supertype of T.
    • Add T values safely.
    • Read values only as Object.
    • Use it for a consumer.
  • 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, Iterable, Comparator, Consumer, and custom generic classes.

Is ? super Integer limited to Number?

No. It can represent Integer, Number, Object, or any other supertype of Integer.

Take Quiz

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

Take Quiz

Keep learning

Related questions

View more