0Pricing
Java Academy · Lesson

The PECS Principle

Producer Extends, Consumer Super.

The PECS Principle is a free Java Academy lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Java Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

What PECS Stands For

PECS means Producer Extends, Consumer Super. It is a mnemonic from Joshua Bloch's Effective Java.

  • If a parameter produces T (you read from it), use ? extends T.
  • If a parameter consumes T (you write to it), use ? super T.
public class Main {
    public static void main(String[] args) {
        System.out.println("Producer Extends, Consumer Super");
    }
}

The Canonical copy Method

The classic PECS example is a copy method. The source produces, so it is ? extends T. The destination consumes, so it is ? super T.

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

public class Main {
    static <T> void copy(List<? extends T> src, List<? super T> dest) {
        for (T t : src) dest.add(t);
    }
    public static void main(String[] args) {
        List<Integer> src = List.of(1, 2, 3);
        List<Object> dest = new ArrayList<>();
        copy(src, dest);
        System.out.println(dest);
    }
}

Why Both Wildcards Add Flexibility

Thanks to PECS, copy works for many type combinations: Integer source into a Number or Object destination, String source into a CharSequence destination.

Without wildcards, the types would have to match exactly, crippling reuse.

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

public class Main {
    static <T> void copy(List<? extends T> src, List<? super T> dest) {
        for (T t : src) dest.add(t);
    }
    public static void main(String[] args) {
        List<Integer> ints = List.of(10, 20);
        List<Number> nums = new ArrayList<>();
        copy(ints, nums);
        System.out.println(nums);
    }
}

A Producer Example

A method that only reads from its collection should use extends. Here we sum a producer.

import java.util.List;

public class Main {
    static double sum(List<? extends Number> producer) {
        double total = 0;
        for (Number n : producer) total += n.doubleValue();
        return total;
    }
    public static void main(String[] args) {
        System.out.println(sum(List.of(1, 2, 3)));
        System.out.println(sum(List.of(1.1, 2.2)));
    }
}

A Consumer Example

A method that only writes to its collection should use super. Here we push values into a consumer.

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

public class Main {
    static void pushDigits(List<? super Integer> consumer) {
        for (int i = 0; i < 5; i++) consumer.add(i);
    }
    public static void main(String[] args) {
        List<Number> nums = new ArrayList<>();
        pushDigits(nums);
        System.out.println(nums);
    }
}

Both Roles: Use a Plain Type

If a parameter is both a producer and a consumer, it should not use a wildcard.

A method that reads and writes the same list needs the exact type T.

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

public class Main {
    static <T> void rotate(List<T> list) {
        if (list.isEmpty()) return;
        T last = list.remove(list.size() - 1);
        list.add(0, last);
    }
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>(List.of(1, 2, 3));
        rotate(list);
        System.out.println(list);
    }
}

PECS in Comparator

The JDK applies PECS pervasively. Collections.max takes a Comparator<? super T> because the comparator consumes T values to compare them.

import java.util.Comparator;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        Comparator<Object> byHash = Comparator.comparingInt(Object::hashCode);
        List<String> words = List.of("a", "bb", "ccc");
        String max = java.util.Collections.max(words, byHash);
        System.out.println(max);
    }
}

PECS in Stream.collect

Many functional interfaces follow PECS. A Consumer<? super T> can accept a handler written for a broader type.

import java.util.List;
import java.util.function.Consumer;

public class Main {
    public static void main(String[] args) {
        Consumer<Object> printer = o -> System.out.println("got: " + o);
        List<String> items = List.of("x", "y");
        items.forEach(printer); // forEach takes Consumer<? super T>
    }
}

Function Producers and Consumers

Function<? super T, ? extends R> is the full PECS form: the input is consumed (super), the output is produced (extends).

This lets a transform accept broader inputs and return narrower outputs.

import java.util.function.Function;

public class Main {
    static <T, R> R apply(Function<? super T, ? extends R> fn, T value) {
        return fn.apply(value);
    }
    public static void main(String[] args) {
        Function<Object, Integer> len = o -> o.toString().length();
        System.out.println(apply(len, "hello"));
    }
}

Return Types: Avoid Wildcards

Do not use wildcards in return types. They force callers to deal with wildcards too, leaking complexity.

Return a concrete type and keep wildcards on parameters where they aid flexibility.

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

public class Main {
    // Good: concrete return type
    static List<Number> doubled(List<? extends Number> src) {
        List<Number> out = new ArrayList<>();
        for (Number n : src) out.add(n.doubleValue() * 2);
        return out;
    }
    public static void main(String[] args) {
        System.out.println(doubled(List.of(1, 2, 3)));
    }
}

A Full PECS Merge

Putting it together: a merge that reads from two producers into one consumer.

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

public class Main {
    static <T> void merge(List<? extends T> a, List<? extends T> b, List<? super T> out) {
        for (T t : a) out.add(t);
        for (T t : b) out.add(t);
    }
    public static void main(String[] args) {
        List<Integer> a = List.of(1, 2);
        List<Integer> b = List.of(3, 4);
        List<Number> out = new ArrayList<>();
        merge(a, b, out);
        System.out.println(out);
    }
}

Quick Check

Apply the PECS rule.

Recap

You learned the PECS principle:

  • Producer Extends: read-only parameters use ? extends T.
  • Consumer Super: write-only parameters use ? super T.
  • Both-roles parameters use a plain type parameter.
  • Avoid wildcards in return types.

Next, applying these ideas to real API design.

public class Main {
    public static void main(String[] args) {
        System.out.println("PECS recap: Producer Extends, Consumer Super");
    }
}

Frequently asked questions

Is the “The PECS Principle” lesson free?

Yes — the full text of “The PECS Principle” is free to read here on the web, and the Java Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Java Academy course, upgrade to CoddyKit PRO.

What will I learn in “The PECS Principle”?

Producer Extends, Consumer Super. You practise Java Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Java Academy?

No prior experience is required. Java Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “The PECS Principle” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Java Academy lesson?

Yes. Every Java Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Upper Bounded Wildcards
  2. Lower Bounded Wildcards
  3. The PECS Principle
  4. Wildcards in APIs
← Back to Java Academy