Question
Java Generic Invariance: Why List<Dog> Is Not List<Animal>
Question
Assume this Java class hierarchy:
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
Given a method that accepts List<Animal>:
void doSomething(List<Animal> animals) {
// ...
}
Why can a List<Dog> or List<Cat> not be passed to this method, even though Dog and Cat are subclasses of Animal? Why must the method explicitly use a wildcard such as List<? extends Animal> to accept lists of subclasses? More generally, why are Java generic types not implicitly polymorphic in this situation?
Short Answer
Java allows a Dog reference where an Animal reference is expected, but it does not make List<Dog> a subtype of List<Animal>. Generic types such as List<T> are invariant by default. This prevents code from inserting a Cat into a list that was created to contain only Dog objects. You can use List<? extends Animal> when a method only needs to read Animal values from a list.
Concept
Java inheritance applies to the objects themselves:
Animal animal = new Dog(); // Valid: Dog is an Animal
It does not automatically apply to generic containers:
List<Dog> dogs = new ArrayList<>();
// List<Animal> animals = dogs; // Does not compile
This rule is called generic invariance. For two types Dog and Animal, even when Dog extends Animal, Java treats List<Dog> and List<Animal> as separate, incompatible types.
The reason is safety. A List<Animal> promises that callers may add any Animal to it, including a Cat. But a List<Dog> must never contain a Cat.
If Java allowed this assignment, the following code would become possible:
Mental Model
Think of a List<Dog> as a kennel labelled Dogs Only. Every item in it must be a dog.
An Animal variable is like a general animal enclosure: it can hold a dog, cat, or another kind of animal. A single dog can safely go into an animal enclosure because it is an animal.
But a dogs-only kennel is not a general animal enclosure. If someone treats it as one, they could put a cat inside it. Java therefore keeps List<Dog> and List<Animal> separate.
List<? extends Animal> means: “a list containing some particular, unknown subtype of Animal.” It might be a dogs-only kennel or a cats-only kennel. You can safely take animals out and view them as Animal, but you cannot safely add a dog or cat because Java does not know which specific subtype the list accepts.
Syntax and Examples
A method that requires a list whose element type is exactly Animal:
import java.util.List;
void addAnimals(List<Animal> animals) {
animals.add(new Dog());
animals.add(new Cat());
}
This method can add both dogs and cats, so it needs a list that is genuinely allowed to contain any Animal.
A method that only reads each value as an Animal should use an upper-bounded wildcard:
import java.util.List;
void printAnimals(List<? extends Animal> animals) {
for (Animal animal : animals) {
System.out.println(animal);
}
}
Now all of these calls are valid:
List<Dog> dogs = List.of(new Dog(), new Dog());
List<Cat> cats = List.of(new Cat());
List<Animal> animals = List.of(new Dog(), new Cat());
printAnimals(dogs);
printAnimals(cats);
printAnimals(animals);
Step by Step Execution
Consider this method:
import java.util.List;
void describeAnimals(List<? extends Animal> animals) {
for (Animal animal : animals) {
System.out.println(animal.getClass().getSimpleName());
}
}
And this call:
List<Dog> dogs = List.of(new Dog(), new Dog());
describeAnimals(dogs);
Step by step:
dogshas the typeList<Dog>.Dogis a subtype ofAnimal.List<? extends Animal>accepts a list of an unknown type that extendsAnimal.- Java can therefore pass
dogstodescribeAnimals. - Within the method, Java knows every item is at least an
Animal. - The loop reads each item into an
Animalvariable safely. - Java does not know whether the original list is a
List<Dog>, , or another subtype list, so it blocks adding a specific animal subtype.
Real World Use Cases
- Displaying domain objects: A UI method that renders names can accept
List<? extends Person>so it can display employees, customers, or administrators. - Processing API results: A reporting function can read
List<? extends Transaction>regardless of the specific transaction subtype returned by a service. - Calculating totals: Methods in Java’s standard library often use
List<? extends Number>when they only need to read numeric values. - Copying data: A destination collection may accept a broader type while a source collection provides a narrower type.
- Plugin systems: A framework can process collections of specific plugin implementations through a shared interface or base class without knowing the concrete implementation type.
Real Codebase Usage
In real Java code, choose a generic parameter based on what the method does with the collection.
Read from a producer: ? extends T
Use ? extends T when the method receives values from the collection and treats them as T.
void sendNotifications(List<? extends Notification> notifications) {
for (Notification notification : notifications) {
notification.send();
}
}
Write to a consumer: ? super T
Use ? super T when the method adds T values to the collection.
void addDefaultDogs(List<? super Dog> destination) {
destination.add(new Dog());
}
The destination may be a List<Dog>, List<Animal>, or List<Object>, because all can store a Dog.
Common Mistakes
Assuming subclass relationships transfer to generic types
This does not compile:
List<Dog> dogs = new ArrayList<>();
// List<Animal> animals = dogs;
Avoid assuming that Container<Child> is a Container<Parent>. With ordinary Java generic classes, it is not.
Using ? extends Animal when items must be added
void addCat(List<? extends Animal> animals) {
// animals.add(new Cat()); // Does not compile
}
Use List<Animal> if the method must add arbitrary Animal subtypes. Use List<? super Cat> if it specifically needs to add cats.
Using raw types to bypass the compiler
List<Dog> dogs = new ArrayList<>();
List unsafe = dogs;
unsafe.add(new Cat());
Raw types disable important generic checks and can cause failures later. Do not use them except when working carefully with unavoidable legacy APIs.
Comparisons
| Type | Can pass List<Dog>? | Can read as Animal? | Can add Dog? | Can add Cat? |
|---|---|---|---|---|
List<Animal> | No | Yes | Yes | Yes |
List<? extends Animal> | Yes | Yes | No | No |
List<? super Dog> | Yes | Only as Object | Yes | No guarantee |
Cheat Sheet
- Java generic types are invariant by default.
Dog extends Animaldoes not implyList<Dog> extends List<Animal>.List<Animal>means the method may read and add anyAnimalsubtype.- Use
List<? extends Animal>to accept lists ofAnimalsubtypes when reading values. - Use
List<? super Dog>to addDogvalues to a destination list. - Use
<T extends Animal>when the method must preserve the precise subtype in its return type or between parameters. - With
? extends T, read values asT; do not add concreteTvalues. - With
? super T, addT; values read back are only guaranteed to beObject. - Memory rule: PECS — Producer Extends, Consumer Super.
- Avoid raw
Listtypes and unchecked casts; they bypass generic safety.
FAQ
Is List<Dog> a subclass of List<Animal> in Java?
No. List<Dog> and List<Animal> are different invariant generic types, even though Dog is an Animal.
Why does Java not make generics covariant automatically?
Automatic covariance would allow code receiving List<Animal> to add a Cat to an object that is actually a List<Dog>. Invariance prevents this type-safety violation.
Why can I pass Dog[] where Animal[] is expected?
Java arrays are covariant for historical reasons. They detect invalid writes at runtime with ArrayStoreException. Generic collections reject the unsafe relationship at compile time.
Can I add anything to List<? extends Animal>?
You cannot safely add a specific Animal subtype because the list may actually be a List<Dog>, List<Cat>, or another subtype list. Technically, is allowed.
Mini Project
Description
Build a small animal shelter utility with methods that inspect animals from subtype-specific lists and add dogs to suitable destination lists. The project demonstrates why reading and writing require different wildcard bounds.
Goal
Use ? extends Animal for safe reading and ? super Dog for safe writing without unsafe casts or raw types.
Requirements
Create Animal, Dog, and Cat classes with a name and a useful toString() method.
Create a method that prints animals from a List<? extends Animal>.
Create a method that adds two dogs to a List<? super Dog>.
Call the reading method with both a dog list and a cat list.
Call the writing method with a List<Animal> and print the resulting list.
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.