LESSON 15 · BEGINNER4 min read

Wildcards, Bounded Generics & The PECS Rule

Master advanced generic variance. Understand invariance, covariance with <? extends T>, contravariance with <? super T>, and Joshua Bloch's PECS rule.

OpenJDK 21+ (LTS)Free to readModule 4 of 15
YOUR NEXT SMALL WINS

By the end, you’ll be able to…

  • Explain why List<Integer> is NOT a subtype of List<Number> (Type Invariance).
  • Apply covariance (<? extends T>) for read-only producer collections.
  • Apply contravariance (<? super T>) for write-only consumer collections using the PECS principle.

01The Invariance Trap

In Java, arrays are covariant: Integer[] is a subtype of Number[]. While this seems intuitive, array covariance is notoriously type-unsafe: you can assign an Integer[] to a Number[] variable and store a Double into it, triggering a runtime ArrayStoreException.

To prevent this catastrophic flaw, Java Generics are Invariant. List<Integer> is distinctly NOT a subtype of List<Number>. Even though Integer extends Number, a List<Integer> cannot be passed to a method expecting List<Number>. If this were allowed, the method could insert a Double into your integer list!

example.js☕Java
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.

02The PECS Principle: Producer Extends, Consumer Super

How do we write flexible, reusable generic methods that accept subtypes or supertypes safely? Joshua Bloch formulated the golden rule of Java generics in Effective Java: PECS (Producer Extends, Consumer Super).

1. Producer Extends (<? extends T>): Use when the collection produces data (you only read from it). You can read items as type T, but you CANNOT add anything to the collection (except null) because the exact concrete subtype is unknown. 2. Consumer Super (<? super T>): Use when the collection consumes data (you only write to it). You can safely write T and its subclasses into the collection, but reading items only yields Object.

example.js☕Java
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.

03Unbounded Wildcards and Bounded Type Parameters

An Unbounded Wildcard (List<?>) represents a list of unknown type. It is useful when method logic only relies on capabilities provided by Object or List methods that do not depend on type parameters (such as list.size() or list.clear()).

Multiple Bounds (<T extends Comparable<T> & Serializable>) allow constraining generic parameters across both classes and interfaces, enabling powerful algorithmic guarantees at compile time.

example.js☕Java
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.

04Why This Matters in Production

The standard Java Collections Framework is the quintessential real-world demonstration of PECS. Inspect Collections.copy(List<? super T> dest, List<? extends T> src): destination consumes data (super), and source produces data (extends).

Failing to use wildcards in library API signatures restricts callers unnecessarily, forcing consumers to copy collections or cast types. Applying PECS makes public APIs intuitive, flexible, and completely type-safe.

05Production Architecture & Engineering Trade-Offs

In large-scale enterprise codebases, rigorous domain modeling relies on enforcing structural invariants at the type level rather than relying on defensive runtime validation. Modern Java leverages sealed types and records to turn runtime type errors into compile-time exhaustiveness guarantees. When designing domain transfer objects or persistent entities, ensuring immutability protects against unintended side effects across concurrent worker threads.

Furthermore, avoiding deep inheritance hierarchies in favor of composition and interface contracts eliminates fragile base class anti-patterns. By encapsulating state mutations within well-defined service boundaries and exposing pure functional transformers, software architects build maintainable, audit-compliant systems that scale smoothly across distributed cloud infrastructure.

LET IT CLICK

The ideas worth keeping.

  • Java Generics are invariant by default to protect collection integrity.
  • Use <? extends T> when you only read from a collection (Producer Extends).
  • Use <? super T> when you only write to a collection (Consumer Super).
TURN UNDERSTANDING INTO SOMETHING YOU CAN DO

Make it yours.

Type and test your code right here in the interactive workspace. Run your solution, check the output, and compare with the worked guide when ready.

01

Exercise 1: Implementing Collections.copy with PECS

Write a generic method copy(List<? super T> dest, List<? extends T> src) that copies elements from src to dest safely.

your_turn_1.java☕Java
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.
Explore a worked solution
one_possible_solution.javaJava
public static <T> void copy(List<? super T> dest, List<? extends T> src) {    for (T item : src) {        dest.add(item);    }}

src is the producer (extends T), while dest is the consumer (super T), fulfilling the PECS rule.

02

Exercise 2: Maximum Element with Bounded Generics

Write a generic method findMax(List<? extends T> list) for any type T that extends Comparable<? super T>.

your_turn_2.java☕Java
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.
Explore a worked solution
one_possible_solution.javaJava
public static <T extends Comparable<? super T>> T findMax(List<? extends T> list) {    if (list.isEmpty()) throw new IllegalArgumentException("Empty list");    T max = list.get(0);    for (T item : list) {        if (item.compareTo(max) > 0) max = item;    }    return max;}

Comparable<? super T> ensures comparison works even if T inherits compareTo from a superclass.

A SMALL QUESTION. A USEFUL NEXT STEP.

What stayed with you?

Check the ideas while they’re fresh. Your answers stay in this page and feedback appears as you go.

A LITTLE ACTIVE RECALL0 / 3 answered
01What does the PECS mnemonic stand for in Java Generics?
02Can you add a new String into a List<? extends CharSequence>?
03Why is List<Integer> not considered a subtype of List<Number> in Java?
⚡CODING CHALLENGE
🔒Required to Mark Lesson Complete

Exercise 1: Implementing Collections.copy with PECS

Write a generic method copy(List<? super T> dest, List<? extends T> src) that copies elements from src to dest safely.

EXPECTED OUTPUT
Build successful. (Java 15)
[Process exited normally]
Main.javaJava
Ctrl + ↵
Execution Console
Click "Run & Verify" (or press Ctrl + Enter) to test your code against the challenge.

Keep exploring.

The official references behind this lesson, when you want a second angle or the full details.

One lesson closer.

Reading is a start. Trying it yourself makes the connection.

Solve the hands-on coding challenge in the knowledge section to verify your code and unlock lesson completion.