Wildcards in Java Generics

Use ?, ? extends T, and ? super T to master covariance and contravariance in Java method parameters.

published: reading time: 17 min read author: Geek Workbench

Wildcards in Java Generics

Wildcards (?) represent an unknown type in generic usage. They are a way to express read patterns and write patterns at the call site without committing to a specific type argument. The three forms cover most scenarios: unbounded (?), upper-bounded (? extends T), and lower-bounded (? super T).

When to Use Wildcards

  • Read-only parameters — use ? extends T when a method only reads from a collection

  • Write-only parameters — use ? super T when a method only writes to a collection

  • Flexible APIs — when a method works with any subtype or supertype of a known type

  • Consumer-Producer pattern — when a parameter both reads and writes, use different bounds for each position (PECS)

When NOT to Use Wildcards

  • Return types — wildcards in return types force callers to deal with unknown types; prefer specific type parameters

  • Class field types — storing a wildcard in a field loses type information permanently

  • Overly complex signatures — chained wildcards like List<? extends List<? extends Number>> are hard to read and usually a sign of over-engineering

Code Example: Upper-Bounded Wildcard (? extends T)


// Reading: we only need to know elements are at least T

public static double sum(List<? extends Number> numbers) {

    double total = 0.0;

    for (Number n : numbers) { // safe read — Number is the upper bound

        total += n.doubleValue();

    }

    return total;

}



// Works with any List of a Number subtype

List<Integer> ints = Arrays.asList(1, 2, 3);

List<Double>  doubles = Arrays.asList(1.1, 2.2);

sum(ints);    // ? extends Number matches Integer

sum(doubles); // ? extends Number matches Double

Code Example: Lower-Bounded Wildcard (? super T)


// Writing: we can accept T or any supertype of T

public static void addNumbers(List<? super Integer> list) {

    list.add(1);    // safe write — we know list holds at least Integer

    list.add(2);

    // list.add(1.5); // compile error — list might be List<Number>

}



List<Number>  numList  = new ArrayList<>();

List<Object>  objList  = new ArrayList<>();

addNumbers(numList); // ? super Integer matches Number

addNumbers(objList); // ? super Integer matches Object

Code Example: PECS (Producer Extends, Consumer Super)


public static <T> void copy(List<? extends T> source, List<? super T> dest) {

    for (T item : source) {   // source produces T (we read it)

        dest.add(item);       // dest consumes T (we write to it)

    }

}



List<Number>  numbers  = new ArrayList<>();

List<Integer> ints     = Arrays.asList(1, 2, 3);

copy(ints, numbers);  // T = Number, source=? extends Integer, dest=? super Number

Code Example: Unbounded Wildcard (?)


public static boolean isEmpty(List<?> list) {

    return list.isEmpty();

    // we only call methods common to all List<?> regardless of element type

}



// List<?> means "a List of some unknown type"

// We can read elements as Object (everything is an Object)

public static void printAll(List<?> list) {

    for (Object item : list) {

        System.out.println(item);

    }

    // list.add("new"); // compile error — we cannot add anything (we don't know the type)

}

Mermaid Diagram: Wildcard Variance


classDiagram

    class Number {

        <<Number>>

    }

    class Integer {

        <<Integer>>

    }

    class Double {

        <<Double>>

    }

    class Object {

        <<Object>>

    }

    Integer --|> Number

    Double --|> Number

    Number --|> Object

    direction TB

    note for "? extends Number" as WC1

    note "Can READ as Number\nCan NOT WRITE (except null)"

    note for "? super Integer" as WC2

    note "Can WRITE as Integer\nCan READ as Object"

    note for "? super Number" as WC3

    note "Can WRITE as Number\nCan READ as Object"

Failure Scenarios

1. Adding Elements to ? extends T


public static void wrongAdd(List<? extends Number> list) {

    list.add(Double.valueOf(1.0)); // compile error!

    // The compiler only knows list is List<X> where X extends Number.

    // It could be List<Integer>, so adding a Double is unsafe.

}



// Fix: if you need to write, use ? super T instead, or do not use wildcards

2. Reading From ? super T


public static void wrongRead(List<? super Integer> list) {

    Integer val = list.get(0); // compile error!

    // The compiler only knows list is List<X> where X super Integer.

    // It could be List<Object>, and Object is not Integer.

    Object val = list.get(0);  // only safe read is Object

}

3. Mixing Read and Write with the Same Wildcard


public static void mixed(List<? extends Number> list) {

    list.add(Integer.valueOf(1)); // compile error

    Number n = list.get(0);       // fine

}

// ? extends T is read-only; ? super T is write-only

// For mixed access, use the type parameter directly: <T> void process(List<T> list)

Trade-Off Table

| Pattern | Use When | Cannot Do |

| ----------------- | ------------------------------------------- | -------------------------------------------------------------- |

| ? extends T | Reading elements as T | Add non-null elements (type unknown) |

| ? super T | Writing elements of type T | Read as anything more specific than Object |

| ? (unbounded) | Operations that need any type info | Read as anything more specific than Object / add any element |

| <T> (parameter) | Needing both read and write, or return type | More verbose call site |

Observability Checklist

  • Confirm wildcard direction matches the data flow (PECS rule)

  • Check return types — wildcards in return types force casts; avoid unless necessary

  • Verify static analysis flags “Exceeds bounds of wildcard” warnings

  • Ensure API documentation describes what wildcards mean for callers (read-only, write-only)

  • Consider refactoring to <T> if a method signature has multiple wildcards that confuse callers

Security Notes

  • Wildcards do not provide runtime type safety: List<? extends Number> and List<Integer> both erase to List at runtime. Untrusted data in collections cannot be protected by wildcards.

  • Unsafe cast via wildcard: List<?> list = new ArrayList<String>(); compiles fine, but adding a non-String and retrieving it will throw ClassCastException. Wildcards let you bypass compile-time checks at your own risk.

  • Reflection and wildcards: Method.getParameterTypes() returns the raw generic type — wildcards are stripped. Passing wildcard-parameterized collections to reflective methods requires extra care.

Pitfalls

  1. ? vs Object in Lists: List<?> and List<Object> are not the same. List<?> has unknown element type so you cannot add anything; List<Object> accepts anything.

  2. Capture confusion: When a wildcard is captured by inference, you may get compiler messages like “capture#1 of ?” — this happens when the compiler infers a specific type for the wildcard but cannot express it.

  3. No nested wildcards for collections: List<List<? extends Number>> is valid but hard to use — you can read inner lists but not add elements to them.

Quick Recap

  • ? extends Tproducer pattern: read elements as T, cannot add elements (except null)

  • ? super Tconsumer pattern: write elements as T, read as Object

  • ? (unbounded) — unknown type, limited to Object reads and no additions

  • PECS: ? extends T for producers (input to your method), ? super T for consumers (output from your method)

  • Wildcards are for call-site flexibility, not storage; return types should use concrete type parameters

  • Type erasure makes wildcards compile-time only — no runtime type information


Key Takeaways

Wildcards solve the asymmetry between invariant generic types and the covariant/contravariant relationships you actually need at call sites. List<String> is not a subtype of List<Object> — generics are invariant by default — but you often want to pass a List<Integer> where a List<? extends Number> is expected. Wildcards make this work without forcing you to abandon type safety.

The PECS rule (Producer Extends, Consumer Super) is the practical heuristic: if your method only reads from a collection, use ? extends T — the compiler lets you treat elements as T. If your method only writes to a collection, use ? super T — the compiler lets you pass elements of type T. Trying to do both with the same wildcard is a compile error for good reason.

The critical insight is that wildcards are for call-site flexibility, not storage. Storing a wildcard in a field permanently discards type information. Return types should never use wildcards — forcing callers to deal with an unknown type is poor API design.

The bounded wildcard ? extends T lets you read as T but not write (except null), because the compiler cannot guarantee the collection’s actual element type. ? super T lets you write as T but only read as Object, because the collection might hold supertypes of T. These are not flaws — they are the type system enforcing that you cannot violate the collection’s element type contract.

For how all this works under the hood, see Type Erasure in Java Generics — wildcards are compile-time constructs that vanish at runtime just like all other generic syntax.


Interview Questions

::: info

These questions use the .qa-card CSS class structure. Each card has a .qa-question and .qa-answer div.

:::

1. What is the difference between ? extends T and ? super T in Java generics?

Model Answer: "`? extends T` is an upper-bounded wildcard — it represents some unknown subtype of `T`. You can **read** elements as `T` but cannot reliably **write** to it (the unknown subtype could reject the value). `? super T` is a lower-bounded wildcard — it represents some unknown supertype of `T`. You can **write** elements of type `T` into it but can only safely **read** as `Object`."

2. What does PECS stand for and when should you use each wildcard?

Model Answer: "PECS stands for **Producer Extends, Consumer Super**. When a method parameter is a collection that your method only reads from (produces elements), use `? extends T`. When your method only writes to it (consumes elements), use `? super T`. When your method does both, use the concrete type parameter `` or a different approach."

3. Why cannot you add elements to a List?

Model Answer: "Because `List` could legally be a `List` or a `List`. The compiler does not know the concrete type at the call site. Adding a `Double` to a `List` would break type safety, so the compiler rejects any addition (except `null`) regardless of the actual subtype."

4. What is the difference between List and List?

Model Answer: "`List` accepts any type — you can add `String`, `Integer`, anything. `List` (unbounded wildcard) accepts `null` but nothing else, because the wildcard represents an unknown type and the compiler refuses to add anything it cannot guarantee is of that unknown type. `List` is concrete; `List` is deliberately restrictive."

5. Can wildcards be used in class declarations?

Model Answer: "No. Wildcards are only for usage sites — method parameters, local variable types, return types. A class must declare a type parameter (e.g., `class Box`), not a wildcard. You can use wildcards when instantiating or when a method accepts a wildcard argument, but the class definition itself uses ``."

6. What is the difference between List and List?

Model Answer: "`List` is invariant — `List` is not a subtype of `List`. `List` is covariant — you can pass a `List` where `List` is expected. However, the wildcard restricts write operations: you cannot add non-null elements to `? extends T` because the compiler does not know the concrete type. `List` allows both reads and writes with full type safety at the call site."

7. How do wildcards behave with nested generic types like List>?

Model Answer: "Syntactically valid but practically problematic. You can read the outer list's elements as `List` but you cannot add elements to those inner lists because the wildcard blocks additions. Deeply nested wildcards are usually a sign of over-engineering. Prefer simpler type parameters or dedicated generic classes for deeply nested scenarios."

8. What does capture conversion mean in the context of wildcards?

Model Answer: "It means the compiler has inferred a specific type for the wildcard at a call site but cannot express that type in source code. This happens in inference chains where the compiler captures the wildcard's inferred type internally for type checking but the type cannot be written back as an explicit type argument. It is an internal compiler artifact — you usually see it when combining generics with method overloads or complex inference scenarios."

9. Can a class declaration use a wildcard as its type parameter?

Model Answer: "No. A class declaration must use a type parameter (`class Box`), not a wildcard. Wildcards are only for usage sites — method parameters, local variable declarations, and return types. You cannot write `class Box` or `class Box`. You can, however, use a wildcard when instantiating: `Box` as a type for a local variable, though this has limited use."

10. How do wildcards interact with Java arrays?

Model Answer: "Arrays in Java are covariant — `String[]` is a subtype of `Object[]`. Wildcards and arrays interact differently: a `List` is not directly representable as an array (you cannot create `Number[]` from `List` at runtime). When a method parameter is an array type with a wildcard bound like `Object[]`, the same covariance rules apply — you can pass a `String[]` but the array's static type is `Object[]`."

11. What is the difference between List and List?

Model Answer: "`List` means "a list of some unknown type" — the compiler knows nothing about the element type, so it only allows reads as `Object` and forbids all additions (except `null`). `List` means "a list of exactly Object" — the compiler allows both reads and writes of any type. `List` is a concrete parameterized type; `List` is a wildcard type with unknown element type. The distinction matters: `List` accepts any specific list; `List` only accepts lists of Object."

12. Can a class implement a generic interface with wildcard usage?

Model Answer: "Yes. When a class implements a generic interface, the method signature can include wildcards. For example, `class StringList implements List` has `add(String)`, not `add(?)`. But you could implement an interface method that uses a wildcard: `void process(List numbers)` inside a class. The wildcard usage is in the method signature, not the class declaration."

13. How does the producer-consumer pattern use wildcards?

Model Answer: "The producer-consumer pattern (PECS) uses wildcards at call sites to express data flow direction. If a method reads from a collection (produces elements), use `? extends T` so the caller can pass `List` for `List`. If a method writes to a collection (consumes elements), use `? super T` so the caller can pass `List` for `List`. When a method does both, use the concrete type parameter instead of wildcards."

14. Can overloaded methods differ only in wildcard bounds?

Model Answer: "Two methods that differ only in wildcard bounds can coexist: `void process(List)` and `void process(List)` are distinct overloads because the wildcard is part of the type signature. However, calling `process(List)` would be ambiguous if both overloads are applicable — the compiler would report an ambiguity error. Prefer concrete type parameters when ambiguity is a concern."

15. What are the pitfalls of using wildcards in varargs?

Model Answer: "Yes, but with caveats. `void printAll(List... lists)` collects varargs into an array. Since arrays and generics interact badly (heap pollution), the compiler warns. `@SafeVarargs` silences the warning. However, `List` elements cannot have anything added to them except `null`, so the method can only read from each list or pass them to other methods that accept wildcards. Be cautious with varargs on generic collections."

16. How does Collections.copy work with super wildcards in practice?

Model Answer: "This is the classic PECS implementation. `src` is `? extends T` — it can produce `T` elements (read as `T`). `dest` is `? super T` — it can consume `T` elements (accept `T` for writing). The `T` from `src` is compatible with `? super T` because any supertype of `T` can hold `T`. The method works because the producer's type is compatible with the consumer's bound, regardless of the concrete types at the call site."

17. What happens when you assign a parameterized type to a wildcard reference?

Model Answer: "`List list = new ArrayList();` compiles but bypasses type safety. You cannot add non-null elements to `list`. If you retrieve elements, they come out as `Object`. Casting `List` to `List` requires an unchecked cast — it compiles with a warning because `List` and `List` are different types at compile time but the same at runtime. Use `Collections.checkedList(list, String.class)` for a runtime-safe view."

18. Should you use wildcards as return types? Why or why not?

Model Answer: "You can, but it is poor API design. Returning `List` forces the caller to deal with an unknown subtype. The caller receives the list but cannot add elements (except `null`) without a compile error. Prefer returning a concrete parameterized type like `List` or the generic type parameter `` when possible. Wildcard return types are acceptable only in very specific scenarios where the caller is expected to only read."

19. How do wildcards relate to Optional types in Java?

Model Answer: "`Optional` does not use wildcards in its core API, but the concepts relate. `Optional` is not a subtype of `Optional` — generics are invariant. If you need a method that accepts `Optional` and works with `Optional`, you cannot use wildcards directly because `Optional` is a value-based class not a collection. Wildcards work with collection-like types where covariance is meaningful for read operations."

20. Can wildcards be used in exception handling or lambda expressions?

Model Answer: "No. You cannot use wildcards in a `catch` clause (`catch (Exception e)` is a compile error). However, generic types can appear in exception declarations: a generic method can throw `T extends Exception`. In lambda expressions, you cannot declare generic exception parameters directly — you wrap them in a generic exception type or use a callback interface that declares the exception type. The JVM's exception handling does not support wildcard types."


Further Reading


Summary

Wildcards (?) are a call-site mechanism for expressing read and write flexibility in generic type usage. ? extends T enables safe reads as T but prohibits writes (except null) because the concrete subtype is unknown. ? super T enables safe writes of T but limits reads to Object. The PECS rule — Producer Extends, Consumer Super — guides which wildcard to use based on whether your method reads from or writes to a collection.

Wildcards are purely a compile-time construct; they vanish at runtime like all generic type information. They are not valid in class declarations, cannot be used as return types, and should not be stored in fields because the type information is permanently lost. For a deeper look at how wildcard variance relates to the underlying type system, see Type Erasure in Java Generics which explains how all generic information disappears at compile time and what that means for type safety.

Category

Related Posts

Abstract Classes in Java

Learn about partially implemented classes that define contracts for subclasses using abstract methods and concrete implementations.

#java-abstract-classes #java #java-fundamentals

Arithmetic Operators in Java

Master Java arithmetic operators: addition, subtraction, multiplication, division, and modulo with integer division gotchas and operator precedence explained.

#java-arithmetic-operators #java #java-fundamentals

Array Basics in Java

Learn Java array fundamentals: declaration, initialization, element access, and the length property explained simply.

#java-array-basics #java #java-fundamentals