Java • Java-Specific Topics

Generic Wildcards — Recognize wildcard usage at a high level (<?>, <? extends T>) (intro)

Learn how Java wildcards make generic code more flexible by accepting a range of types safely.

Java programming emblem with a coffee cup, code braces, and terminal
Inside this lesson 10 sections 5 questions 5 figures

SECTION 1

Historical Context & Motivation

Before Java introduced generics, programmers stored objects in collections like ArrayList without any type information. Every item that came out of a collection was just an Object, and you had to manually cast it to the correct type. This led to runtime errors that the compiler could never catch for you. Generics fixed that problem by letting you declare what type a collection holds, such as ArrayList<String>. But generics introduced a new question: what do you do when you want to write a method that works with any kind of list, regardless of the type inside it?

That question is exactly what wildcard types were designed to answer. Wildcards, written with the ? symbol, allow you to write flexible code that accepts a variety of generic types while keeping the safety guarantees that generics provide. Let's trace the timeline of how this feature came to be.

1995 • Java 1.0 Released

Java launches without generics. All collection classes store raw Object references, requiring manual casting and offering no compile-time type safety.

1998 • Generic Java (GJ) Proposed

Researchers including Philip Wadler, Martin Odersky, and Gilad Bracha propose adding parameterized types to Java, laying the groundwork for today's generics.

2004 • Java 5 — Generics & Wildcards Arrive

Java 5 (J2SE 5.0) officially introduces generics with full wildcard support: <?>, <? extends T>, and <? super T>. This becomes one of the most significant language updates in Java's history.

2014–Present • Generics Mature with Java 8+

Lambda expressions and the Streams API in Java 8 make heavy use of wildcards internally, proving that wildcards are essential infrastructure in modern Java programming.

The core question that wildcards address is this: if Dog is a subclass of Animal, why isn't List<Dog> a subtype of List<Animal>? Understanding this puzzle is the key to understanding why wildcards exist.

SECTION 2

Core Principles & Definitions

To understand wildcards, you first need to grasp a counterintuitive rule in Java generics called invariance. Even though Integer extends Number, a List<Integer> is not a subtype of List<Number>. Java enforces this to prevent type-safety violations. Wildcards are the tool that lets you relax this strict rule in a controlled way.

1

Unbounded Wildcard <?>

Represents any type. A method accepting List<?> can take a List<String>, List<Integer>, or any other list. You can read from it, but you cannot safely add elements (other than null).
2

Upper-Bounded Wildcard <? extends T>

Restricts the unknown type to be T or any subclass of T. For example, List<? extends Number> accepts List<Integer>, List<Double>, etc. Great for reading data from a collection.
3

Invariance Problem

Without wildcards, List<Dog> cannot be passed where List<Animal> is expected. Java generics are invariant by default — the type parameter must match exactly. Wildcards provide controlled flexibility.
4

Type Safety Guarantee

Wildcards never break type safety. The compiler restricts what operations you can perform. With <? extends T>, you can safely read elements as type T, but you cannot add new elements because the exact subtype is unknown.
✦

KEY TAKEAWAY

Think of wildcards like a museum visitor badge. An unbounded wildcard <?> is like a badge that says "any visitor" — you can observe what's inside the exhibit, but you can't rearrange the displays. An upper-bounded wildcard <? extends T> is like a badge that says "science department visitors only" — you know everyone inside is a scientist, so you can treat them as scientists, but you still can't add random people to the group.

SECTION 3

Visual Explanation — The Type Hierarchy

The diagram below shows how Java's type hierarchy works for the Number family and how wildcards let you write methods that accept multiple list types at once. Notice that without wildcards, each List<X> type is completely separate from every other List<Y> type, even if X is a subclass of Y.

Class Hierarchy vs. Generic Type RelationshipsNumberIntegerDoubleFloat↓ With Generics (No Wildcards) — Invariant ↓List<Integer>List<Double>List<Float>✗✗These are NOT subtypes of each other!↓ With Wildcard — List<? extends Number> ↓List<? extends Number>✓ All three list types are accepted!The wildcard acts as an umbrella that covers all subtypes.
The top section shows the normal class hierarchy where Integer, Double, and Float extend Number. The middle section shows that their List types are completely separate (invariant). The bottom shows how List<? extends Number> acts as an umbrella accepting all of them.

As you can see, the red X marks indicate that List<Integer> and List<Double> have no subtype relationship with each other, even though Integer and Double are siblings under Number. The wildcard <? extends Number> solves this by creating a flexible type that encompasses all lists whose element type is Number or any of its subclasses.

SECTION 4

How Wildcards Work Under the Hood

When the Java compiler encounters a wildcard, it applies specific rules about what operations are allowed. These rules are enforced at compile time — by the time your program runs, the wildcard information has been erased (a process called type erasure). This means wildcards are a purely compile-time mechanism for catching errors early.

Unbounded Wildcard: <?>

When you write List<?>, you are saying "a list of some unknown type." The compiler allows you to read elements from this list as Object (since every type extends Object), but it forbids you from adding anything (except null). This is because the compiler cannot verify that whatever you add is compatible with the unknown actual type.

⚠

Why can't you add to List<?>?

Imagine List<?> is actually a List<String> at runtime. If Java let you call list.add(42), you'd be putting an Integer into a list of Strings — a type-safety disaster. The compiler prevents this at compile time.

Upper-Bounded Wildcard: <? extends T>

When you write List<? extends Number>, you're saying "a list of some type that is Number or a subclass of Number." This is more useful than <?> because now you can read elements and treat them as Number objects. You can call methods like .doubleValue() on anything you pull out. However, the same add restriction applies — you still cannot put items into the list because the exact subtype is unknown.

OperationList<?>List<? extends Number>List<Number>
Read elements✓ as Object✓ as Number✓ as Number
Add elements✗ (only null)✗ (only null)✓ any Number
Accept List<Integer>✓✓✗
Accept List<String>✓✗✗

Comparison of allowed operations for different generic declarations

SECTION 5

Wildcard Types at a Glance

Java supports three kinds of wildcards, though this introductory lesson focuses on the first two. The diagram below gives you a visual map of all three types and when you'd use each one. Think of wildcards as different levels of specificity in a search filter.

The Three Wildcard Types<?>UnboundedAccepts ANY typeRead as: ObjectWrite: ✗ (only null)Use: "I don't careabout the type"<? extends T>Upper-BoundedAccepts T or subclassesRead as: TWrite: ✗ (only null)Use: "I want to READitems as type T"<? super T>Lower-BoundedAccepts T or superclassesRead as: ObjectWrite: ✓ items of type TUse: "I want to WRITEitems of type T"Quick Decision GuideDo you need to READ items from the collection?YESNOUse <? extends T>Use <? super T>Tip: This is known as the PECS rule — Producer Extends, Consumer Super
The three wildcard types side by side. For this intro lesson, focus on the first two columns: <?> and <? extends T>. The lower-bounded wildcard <? super T> is shown for completeness and will be covered in a future lesson.

The decision guide at the bottom of the diagram introduces a helpful mnemonic that many Java developers use: PECS, which stands for Producer Extends, Consumer Super. If a collection produces data for you to read, use extends. If it consumes data that you write into it, use super. For now, just remember the "extends" half of this rule.

SECTION 6

Worked Example — Summing a List of Numbers

Let's walk through a realistic example. Suppose you want to write a method called sumAll that takes a list of any kind of number — Integer, Double, Float — and returns the sum as a double. Without wildcards, you'd need to write a separate method for each number type. With an upper-bounded wildcard, one method handles them all.

Writing a Flexible sumAll Method

  1. 1

    Step 1 — Identify the Problem

    We want a method that accepts List<Integer>, List<Double>, or any List of a Number subtype. A parameter of type List<Number> will NOT work because List<Integer> is not a subtype of List<Number>.
  2. 2

    Step 2 — Choose the Right Wildcard

    Since we want to read numbers from the list (it's a producer), we use <? extends Number>. This allows the method to accept any list whose element type is Number or a subclass.
    Use List<? extends Number> as the parameter type.
  3. 3

    Step 3 — Write the Method Signature

    The method signature becomes: public static double sumAll(List<? extends Number> numbers). Notice that the return type is a simple double — we're summing the values, so we don't need generics in the return type.
  4. 4

    Step 4 — Implement the Method Body

    Inside the method, each element retrieved from numbers is guaranteed to be at least a Number, so we can call .doubleValue() on it. The complete code is: double total = 0; for (Number n : numbers) { total += n.doubleValue(); } return total;
  5. 5

    Step 5 — Test with Different List Types

    Now you can call sumAll(listOfIntegers) and sumAll(listOfDoubles) with the exact same method. Both compile and run correctly.
    One method works for all Number subtypes — no code duplication!
⚠

Common Mistake

Beginners often try public static double sumAll(List<Number> numbers) and then wonder why sumAll(myIntegerList) causes a compile error. Remember: List<Integer> is NOT a List<Number>. You must use List<? extends Number> to make it flexible.

SECTION 7

Wildcards vs. Concrete Generics vs. Raw Types

It can be confusing to know when to use wildcards versus a regular generic type parameter versus a raw type. The table below compares these three approaches and highlights the trade-offs of each.

FeatureRaw Type (List)Concrete Generic (List<Number>)Wildcard (List<? extends Number>)
Type safety❌ None — errors at runtime✅ Full — errors at compile time✅ Full — errors at compile time
Accepts subtypes✅ Accepts anything❌ Only exact type✅ T and all subtypes of T
Can read as T❌ Only as Object (with cast)✅ Yes✅ Yes
Can add items✅ (unsafe)✅ (safe)❌ Cannot add (except null)
Recommended?Never use in new codeWhen you know the exact typeWhen method should be flexible

Comparison of raw types, concrete generics, and wildcard generics

✦

KEY TAKEAWAY

Think of it like ordering food. A raw type is like ordering "food" with no specification — you might get anything, and it could be terrible. A concrete generic is like ordering "pepperoni pizza" — you know exactly what you'll get, but that's the only option. A wildcard is like ordering "any kind of pizza" — you're guaranteed it's pizza, but you're flexible about the toppings.

SECTION 8

Connection to Advanced Generics

This lesson introduced <?> and <? extends T> as your first steps into wildcard territory. As you continue your Java journey, you'll encounter more powerful generics features that build on these foundations. The table below shows what comes next.

What You Learned (Intro)What Comes Next (Advanced)
<?> — unbounded wildcard<? super T> — lower-bounded wildcard (lets you write to a collection)
<? extends T> — upper-bounded wildcardFull PECS rule (Producer Extends, Consumer Super) for designing APIs
Using wildcards in method parametersWriting your own generic classes and interfaces with bounded type parameters
Understanding invarianceType erasure details, bridge methods, and reifiable types

Roadmap from intro wildcards to advanced generics

One particularly important advanced concept is the lower-bounded wildcard <? super T>. While <? extends T> makes a collection readable but not writable, <? super T> does the opposite — it makes a collection writable but limits what you can read from it. Together, these two bounded wildcards form the full PECS pattern that professional Java developers rely on daily.

💡

Looking Ahead

If you go on to study Java's Streams API (like stream.map() and stream.filter()), you'll see wildcards everywhere in the library's source code. The concepts you learned today are the foundation for understanding how these powerful tools are built.

SECTION 9

Practice Problems

Question 1: CONCEPTUAL

Explain in your own words why List<Integer> is NOT a subtype of List<Number> in Java, even though Integer IS a subtype of Number. What could go wrong if Java allowed this?
Show answer
If List<Integer> were a subtype of List<Number>, you could pass a List<Integer> to a method that expects List<Number>, and that method could legally add a Double to it — because Double is also a Number. This would corrupt the list by placing a non-Integer into what the original code believes is a list of only Integer objects. Later, when someone reads from the list and casts to Integer, a ClassCastException would occur at runtime. This is exactly the kind of bug generics are designed to prevent, which is why Java enforces invariance.

Question 2: BASIC

Which of the following method signatures would allow you to pass both a List<String> and a List<Integer> to the same method? (a) void print(List<Object> items) (b) void print(List<?> items) (c) void print(List<? extends Number> items)
Show answer
The correct answer is (b). List<?> is the unbounded wildcard, meaning it accepts a list of any type — so both List<String> and List<Integer> qualify. Option (a) fails because generics are invariant — List<String> is NOT a subtype of List<Object>. Option (c) fails for List<String> because String does not extend Number.

Question 3: INTERMEDIATE

Consider the following method: public static double findMax(List<? extends Number> list). Inside this method, can you (a) read elements and call .doubleValue() on them? (b) add a new Integer to the list? Explain why for each.
Show answer
(a) Yes, you can read elements and call .doubleValue(). Since the wildcard has an upper bound of Number, the compiler knows every element is at least a Number, and doubleValue() is defined in the Number class. (b) No, you cannot add an Integer (or any other value except null). The actual list might be a List<Double> at runtime, and putting an Integer into it would violate type safety. The compiler blocks this at compile time.

Question 4: APPLIED

You are building an app that stores shapes. You have classes Circle, Rectangle, and Triangle, all extending a Shape superclass that has a getArea() method. Write a method signature (not the full body) for totalArea that accepts a list of any kind of shape and returns the total area as a double. Explain why you chose that wildcard.
Show answer
The method signature should be: public static double totalArea(List<? extends Shape> shapes). We use <? extends Shape> because the method needs to read each shape and call getArea() on it — making the list a producer. The upper-bounded wildcard ensures that the method can accept List<Circle>, List<Rectangle>, or List<Triangle> without requiring separate method overloads. Using List<Shape> alone would not accept List<Circle> due to invariance.

Question 5: CRITICAL THINKING

A classmate writes: "I don't understand why we need wildcards at all. Why not just use Object everywhere, like List<Object>?" Write a response to your classmate that addresses at least two specific problems with their suggestion and explains how wildcards solve those problems.
Show answer
There are two major problems with using List<Object>. First, due to invariance, a method that takes List<Object> still cannot accept List<String> or List<Integer> — so the flexibility problem isn't actually solved. Second, even if you somehow got items into a List<Object>, you'd lose all type information. Every item you read would be an Object, and you'd need to cast it back to the real type, risking ClassCastExceptions at runtime. Wildcards solve both problems: List<? extends Number> truly accepts List<Integer> and others, AND it preserves type information so you can work with elements as Number objects without casting.

SUMMARY

Lesson Summary

Summary

Java generic wildcards solve the invariance problem — the fact that List<Integer> is not a subtype of List<Number> even though Integer extends Number. The unbounded wildcard <?> accepts a list of any type, letting you perform type-agnostic operations like printing or counting. The upper-bounded wildcard <? extends T> restricts the unknown type to T or its subclasses, allowing you to safely read elements as type T.

The key trade-off is that wildcard types are read-only — you cannot add elements to a collection declared with <?> or <? extends T> because the compiler cannot verify the exact type. Remember the PECS mnemonic: Producer Extends, Consumer Super. When a collection produces data for you to read, use extends. Wildcards give you the flexibility of accepting multiple generic types while preserving the type safety that makes Java generics so valuable.

Varsity Tutors • Java • Generic Wildcards — Recognize wildcard usage at a high level (<?>, <? extends T>) (intro)