Question

Java Generics: List<? super T> vs List<? extends T>

javagenericscollections

Question

What is the difference between List<? super T> and List<? extends T> in Java generics?

I previously used List<? extends T>, but Java does not allow me to add an element such as list.add(element) to that list. However, List<? super T> allows adding T values. Why does this happen, and when should each wildcard be used?

Short Answer

By the end of this page, you will understand Java's upper-bounded (? extends T) and lower-bounded (? super T) wildcards. You will know why extends is appropriate for reading T values, why super is appropriate for adding T values, and how the PECS rule helps you choose between them.

Concept

A wildcard describes an unknown type in a generic collection.

  • List<? extends T> means: “a list whose element type is some unknown subtype of T.”
  • List<? super T> means: “a list whose element type is some unknown supertype of T.”

Assume this class hierarchy:

class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}

A List<? extends Animal> may actually be a List<Dog>, List<Cat>, or List<Animal>.

Because it might be a List<Dog>, Java cannot safely let you add an arbitrary Animal:

List<? extends Animal> animals = new ArrayList<Dog>();
// animals.add(new Cat()); // Would be unsafe if the real list is List<Dog>

However, every item read from that list is guaranteed to be at least an Animal, so reading is safe.

A List<? super Dog> may actually be a List<Dog>, List<Animal>, or List<Object>.

Each of those lists can safely store a Dog, so adding a Dog is safe. But when reading, Java can guarantee only that the value is an Object, because the actual list might be a List<Object>.

This is summarized by PECS:

  • Producer Extends: use ? extends T when a structure produces or supplies T values to your code.
  • Consumer Super: use ? super T when a structure consumes or receives T values from your code.

The rule is about the direction values move through a method parameter, not whether the collection itself is inherently read-only or write-only.

Mental Model

Imagine a List<? extends Animal> as a sealed box labeled “contains a kind of animal.” It could contain only dogs, only cats, or mixed animals. You may take out an Animal, because every possible box contains animals. But you cannot put a cat in: the box could be a dogs-only box.

Imagine a List<? super Dog> as a container guaranteed to accept dogs. It might be a dog container, an animal container, or an object container. You can place a dog into all of them. When taking something out, though, the only label you can trust is Object.

Take Quiz

Syntax and Examples

Core syntax

List<? extends T> source;
List<? super T> destination;

For a concrete example using Animal and Dog:

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

class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}

public class WildcardExample {
    public static void main(String[] args) {
        List<Dog> dogs = new ArrayList<>();
        dogs.add(new Dog());

        List<? extends Animal> animalProducer = dogs;
        Animal animal = animalProducer.get(0); // Safe: every element is an Animal
        // animalProducer.add(new Dog());       // Not allowed

        List<Animal> animals = new ArrayList<>();
        List<? super Dog> dogConsumer = animals;
        dogConsumer.add(new Dog());             // Safe
        Object value = dogConsumer.get(0);      // Only Object is guaranteed
    }
}

animalProducer might refer to List<Dog>, so adding even a Dog is rejected: Java does not know the list's exact element type through that reference.

dogConsumer can refer to a List<Dog>, List<Animal>, or List<Object>. All of those can accept a Dog.

Step by Step Execution

Consider a method that copies dogs into a compatible destination:

import java.util.List;

class Animal {}
class Dog extends Animal {}

static void copyDogs(List<? extends Dog> source, List<? super Dog> destination) {
    for (Dog dog : source) {
        destination.add(dog);
    }
}

Step by step:

  1. source can be a List<Dog> or a list of a subclass of Dog.
  2. When the loop reads an item from source, Java guarantees that it is a Dog, so Dog dog is valid.
  3. destination can be a List<Dog>, List<Animal>, or List<Object>.
  4. Every valid destination can store a Dog, so destination.add(dog) is valid.
  5. The method can safely be called with List<Dog> as the source and List<Animal> as the destination.
List<Dog> dogs = List.of(new Dog(), new Dog());
List<Animal> animals = new ArrayList<>();

copyDogs(dogs, animals);

The source produces dogs for the method; the destination consumes dogs from the method.

Real World Use Cases

  • Copying data: Use ? extends T for a source collection and ? super T for a destination collection.
  • Sorting: Java's Collections.sort accepts comparators using lower bounds because a comparator of a broader type can compare narrower values.
  • Event handlers: A method that registers handlers for Dog events can often accept a handler for Animal events. The handler consumes dogs, so a super bound is useful.
  • Numeric calculations: A method that reads numbers without modifying the input may accept List<? extends Number>, allowing List<Integer>, List<Double>, and similar lists.
  • Bulk insertion: A method that inserts Dog instances can accept Collection<? super Dog>, allowing callers to provide collections of dogs, animals, or objects.
Take Quiz

Real Codebase Usage

Developers usually place wildcards on method parameters, where they make APIs more flexible without weakening type safety.

Read from a flexible source

static double sum(List<? extends Number> numbers) {
    double total = 0;
    for (Number number : numbers) {
        total += number.doubleValue();
    }
    return total;
}

This method works with List<Integer>, List<Long>, and List<Double> because it only reads Number values.

Write to a flexible destination

static void addDefaultDogs(List<? super Dog> destination) {
    destination.add(new Dog());
    destination.add(new Dog());
}

This works with List<Dog>, List<Animal>, and List<Object>.

Common API pattern: copy

static <T> void copy(List<? extends T> source, List<? super T> destination) {
    for (T item : source) {
        destination.add(item);
    }
}

Use a plain List<T> when the method must both read and add values as exactly T, or when callers should provide exactly that type. Use wildcards only when variance is needed.

Common Mistakes

Assuming extends means the list is immutable

List<? extends T> is not necessarily immutable. The original list may be mutable, but this particular wildcard reference cannot safely add non-null elements.

List<Dog> dogs = new ArrayList<>();
List<? extends Animal> animals = dogs;

// animals.add(new Dog()); // Compile-time error

You may still remove through the reference if the underlying list supports removal, and you may add null because null fits every reference type. In practice, avoid relying on null insertion.

Expecting to read T from ? super T

This does not compile:

List<? super Dog> dogs = new ArrayList<Animal>();
// Dog dog = dogs.get(0); // Compile-time error

The actual list might be List<Object>, whose elements are not necessarily dogs. Read it as Object instead.

Object value = dogs.get(0);

Thinking ? extends Animal accepts only List<Animal>

It also accepts lists of subclasses:

List<Dog> dogs = new ArrayList<>();
List<? extends Animal> animals = dogs;

That flexibility is the main reason to use an upper bound.

Thinking ? super Dog accepts List<Cat>

It does not. Cat is not a supertype of Dog.

List<Cat> cats = new ArrayList<>();
// List<? super Dog> destination = cats; // Compile-time error

Using wildcards on fields without a need

Wildcard fields can make later use awkward. Prefer a concrete generic type for stored state when possible, and use bounded wildcards primarily in method parameters.

Comparisons

DeclarationPossible actual list typesSafely read asSafely add
List<T>Only List<T>TT
List<? extends T>List<T> or a list of a subtype of TTNothing except null
List<? super T>List<T> or a list of a supertype of TObjectT and subclasses of T
List<?>A list of any reference typeObjectNothing except null

extends is an upper bound: the unknown element type is T or below it in the inheritance hierarchy.

super is a lower bound: the unknown element type is T or above it in the inheritance hierarchy.

For example, if Dog extends Animal:

List<? extends Animal> a = new ArrayList<Dog>(); // Valid
List<? super Dog> b = new ArrayList<Animal>();   // Valid

Neither wildcard changes the objects in the list. It changes what the compiler can prove is safe through that reference.

Take Quiz

Cheat Sheet

// Read T values from a source
List<? extends T> source;
T item = source.get(0);

// Add T values to a destination
List<? super T> destination;
destination.add(item);

// Copy pattern
static <T> void copy(List<? extends T> source, List<? super T> destination) {
    for (T item : source) {
        destination.add(item);
    }
}
  • Use ? extends T when the parameter produces T values.
  • Use ? super T when the parameter consumes T values.
  • From ? extends T, read as T; do not add non-null values.
  • To ? super T, add T or subclasses; read only as Object.
  • Remember: PECS = Producer Extends, Consumer Super.
  • Prefer List<T> when you need both reliable reads as T and writes of T through the same reference.

FAQ

Why can't I add a T to List<? extends T>?

The list might actually be a List<SubclassOfT>. Adding a plain T could violate that list's element type.

Can I add a subclass of T to List<? extends T>?

No. The unknown list type could be a different subclass. For example, a List<? extends Animal> could be a List<Cat>, so adding a Dog would be unsafe.

Why can I add T to List<? super T>?

The actual list type is guaranteed to be T or one of its supertypes. All such lists can store a T.

Why does get() return Object from List<? super T>?

The actual list could be List<Object>, which may contain values that are not T. Therefore, Object is the only guaranteed type.

Can List<? super T> accept subclasses of T?

Yes. If a list can store T, it can also store any subtype of T.

Does ? extends T mean inheritance only?

It is based on Java's subtype relationship. It includes T itself as well as its subclasses and implementing types where applicable.

Should I always use PECS?

Use it when a method needs to accept collections with compatible element types. If exact typing is clearer and sufficient, use List<T> instead.

Take Quiz

Mini Project

Description

Build a small utility that copies records from a source list into a destination list. It demonstrates the PECS pattern used by many collection APIs: a source produces values and a destination consumes them.

Goal

Create a type-safe method that copies Dog values from compatible source lists into compatible destination lists.

Requirements

  1. Create Animal, Dog, and Poodle classes, where Dog extends Animal and Poodle extends Dog.
  2. Write a copyDogs method that accepts a source list and a destination list.
  3. Allow a List<Poodle> to be used as the source.
  4. Allow a List<Animal> and a List<Object> to be used as destinations.
  5. Copy every source item into each destination and print the resulting sizes.
Take Quiz

Keep learning

Related questions

View more