Back to Generics & Type Safety
Q5Question 5 of 7

What is wildcard capture?

Key Concepts
  • List<?> (unbounded wildcard) means a list of some unknown type; you can read elements as Object but cannot add anything except null.
  • The compiler blocks writes because it does not know the actual type: if ? is Integer, adding a String would corrupt the list.
  • Wildcard capture is the technique of creating a private helper method with a named type parameter <T> that captures the wildcard ? as a concrete type.
  • Inside the capture helper, the compiler treats T as a specific (though unknown) type, enabling both reads and writes.
  • The JDK uses this pattern in Collections.swap(), Collections.fill(), and Collections.copy().
  • The capture helper must be a separate method because the capture happens at the method call boundary where the compiler infers T from the argument.
  • Wildcard capture errors show the message 'capture#1 of ?' in compiler output, indicating the compiler cannot resolve the wildcard to a concrete type.

Definition

List<?> is read-only because the compiler does not know the actual element type, and the wildcard capture helper pattern uses a private generic method to capture ? as a named type T, enabling type-safe reads and writes.
  • List – unbounded wildcard meaning "list of some unknown type"; elements come out as Object; only null can be added
  • Write restriction – the compiler blocks list.add(x) because if ? is Integer, adding a String would corrupt the list; the compiler cannot verify safety
  • Capture helper – a private method with that receives List instead of List; the compiler captures the wildcard as T at the call site
  • Inside the helper – T is a concrete type, so both list.get() (returns T) and list.set(i, value) (accepts T) are type-safe
  • JDK usage – Collections.swap, Collections.fill, and other utility methods use this pattern internally

At National Bank of India, List is like a sealed envelope containing a list of some type of financial document. You can peek inside (read as Object), but you cannot insert a new document because you do not know whether the envelope contains cheques or loan forms. The capture helper is like asking a trusted clerk (generic method) to open the envelope. The clerk sees the actual document type (T) and can safely read and insert documents of that type.

Understanding the Concepts

What is wildcard capture?

When a method accepts List, it can read elements as Object but cannot add elements (except null). This is because the compiler does not know what ? represents. The wildcard capture pattern solves this by delegating to a private generic method where the compiler captures ? as a named type parameter T.

Why can you not write to List?

The compiler blocks writes to List because it cannot guarantee type safety. The list might be a List, a List, or a List. Adding any specific type could corrupt the list.

JAVA
package com.bank.demo;

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

public class WildcardWriteRestrictionDemo {
    static void process(List<?> list) {
        // READ: OK, elements come out as Object
        Object first = list.get(0);
        System.out.println("Read: " + first);

        // WRITE: blocked by compiler
        // list.add("test");    // COMPILE ERROR
        // list.add(42);        // COMPILE ERROR
        list.add(null);          // OK: null is always safe

        // Size, isEmpty, contains: all OK
        System.out.println("Size: " + list.size());
    }

    public static void main(String[] args) {
        List<String> accounts = new ArrayList<>(List.of("SAV2001", "CUR3001"));
        process(accounts);

        List<Integer> amounts = new ArrayList<>(List.of(10000, 25000));
        process(amounts);
    }
}
TEXT
Read: SAV2001
Size: 2
Read: 10000
Size: 2

How does the capture helper pattern work?

The pattern involves two methods. The public method accepts List and delegates to a private generic helper that accepts List. At the call site, the compiler infers T from the actual argument type, capturing the wildcard.

JAVA
package com.bank.demo;

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

public class CaptureHelperDemo {

    // Public method with wildcard: cannot do set() directly
    public static void swap(List<?> list, int i, int j) {
        // list.set(i, list.get(j)); // COMPILE ERROR: capture#1 of ?
        swapHelper(list, i, j);      // delegate to capture helper
    }

    // Private capture helper: compiler captures ? as T
    private static <T> void swapHelper(List<T> list, int i, int j) {
        T temp = list.get(i);    // read as T: OK
        list.set(i, list.get(j)); // write T: OK
        list.set(j, temp);        // write T: OK
    }

    public static void main(String[] args) {
        List<String> accounts = new ArrayList<>(List.of("SAV2001", "CUR3001", "FD4001"));
        System.out.println("Before swap: " + accounts);

        swap(accounts, 0, 2);
        System.out.println("After swap: " + accounts);
    }
}
TEXT
Before swap: [SAV2001, CUR3001, FD4001]
After swap: [FD4001, CUR3001, SAV2001]

How does the JDK use wildcard capture?

The JDK uses this pattern in several utility methods. Here is a practical example similar to Collections.reverse.

JAVA
package com.bank.demo;

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

public class ReverseDemoWithCapture {

    public static void reverse(List<?> list) {
        reverseHelper(list);
    }

    private static <T> void reverseHelper(List<T> list) {
        int size = list.size();
        for (int i = 0; i < size / 2; i++) {
            T temp = list.get(i);
            list.set(i, list.get(size - 1 - i));
            list.set(size - 1 - i, temp);
        }
    }

    public static void main(String[] args) {
        List<String> accounts = new ArrayList<>(List.of("SAV2001", "CUR3001", "FD4001"));
        reverse(accounts);
        System.out.println("Reversed: " + accounts);
    }
}
TEXT
Reversed: [FD4001, CUR3001, SAV2001]

What is the difference between List and List?

| Feature | List | List |
|---|---|---|
| Accepts List? | Yes | No (invariance) |
| Read elements | As Object | As Object |
| Write elements | Only null | Any Object |
| Use case | Generic utility methods | Truly heterogeneous list |

JAVA
package com.bank.demo;

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

public class WildcardVsObjectDemo {
    static void wildcardMethod(List<?> list) {
        // list.add("test"); // COMPILE ERROR
    }

    static void objectMethod(List<Object> list) {
        list.add("test"); // OK: List<Object> accepts Object
    }

    public static void main(String[] args) {
        List<String> strings = new ArrayList<>(List.of("SAV2001"));

        wildcardMethod(strings);   // OK: List<?> accepts any List
        // objectMethod(strings);  // COMPILE ERROR: List<String> != List<Object>
    }
}

What are common mistakes?

Mistake: Trying to write to List directly

JAVA
// WRONG: cannot add to List<?>
static void addDefault(List<?> list) {
    list.add("DEFAULT"); // COMPILE ERROR
}
JAVA
// CORRECT: use capture helper
static void addDefault(List<?> list) {
    addDefaultHelper(list);
}
private static <T> void addDefaultHelper(List<T> list) {
    // This still does not work for adding a String to List<T>
    // because T might not be String.
    // The real fix depends on the use case.
}

The key insight is that the capture helper does not magically let you add arbitrary types. It lets you perform operations that are inherently type-safe (like swapping two elements from the same list) by giving the wildcard a name.

What should a senior engineer know?

  • Capture errors in compiler output. The error message "capture#1 of ?" means the compiler tried to capture the wildcard but could not resolve it to a concrete type for the operation.
  • OpenJDK implementation. Some JDK methods (like Collections.swap) use raw types internally instead of the capture helper for historical reasons. New code should use the generic helper pattern.
  • Capture with bounded wildcards. The pattern works with ? extends T and ? super T as well. The helper captures the exact bound.
  • Design guideline. If a public API uses wildcards, keep the implementation in a private generic helper. This gives callers maximum flexibility while maintaining internal type safety.

What are common interview follow-up questions?

  • "Why can you not add to List?" : The compiler does not know the actual type of ?, so it cannot verify that any addition is type-safe.
  • "What is the only thing you can add to List?" : null, because null is assignment-compatible with every reference type.
  • "What is the capture helper pattern?" : A private generic method that captures the wildcard as a named type parameter, enabling type-safe operations.
  • "Does the JDK use this pattern?" : Yes. Collections.swap, Collections.fill, and Collections.copy use capture helpers internally.

Visual Overview

DIAGRAM
  Wildcard Capture Pattern
  ========================

  Public method:                     Private capture helper:
  ┌──────────────────────────┐       ┌──────────────────────────────┐
  │ void swap(List<?> list)  │       │ <T> void helper(List<T> list)│
  │                          │       │                              │
  │ list.get(0) -> Object    │       │ list.get(0) -> T             │
  │ list.set(0, x) -> ERROR  │ ────▶ │ list.set(0, x) -> OK         │
  │                          │       │                              │
  │ Compiler does not know ? │       │ Compiler captures ? as T     │
  └──────────────────────────┘       └──────────────────────────────┘

  How capture works:
  ┌────────────────────────────────────────────┐
  │ swap(myStringList, 0, 1)                    │
  │                                             │
  │ List<?> list = myStringList (? = String)    │
  │ swapHelper(list, 0, 1)                      │
  │   -> compiler infers T = String             │
  │   -> swapHelper(List<String> list, 0, 1)    │
  │   -> T temp = list.get(0) -> String         │
  │   -> list.set(0, list.get(1)) -> OK         │
  └────────────────────────────────────────────┘

Key Takeaway

List is a list of some unknown type. You can read elements from it as Object, but you cannot add anything except null because the compiler does not know the actual element type. If the list is really a List, adding a String would corrupt it, so the compiler blocks all writes.

The wildcard capture helper pattern solves this limitation. By creating a private generic method with a named type parameter T, the compiler captures the wildcard as T at the call boundary. Inside the helper, T is a concrete type, enabling type-safe reads and writes. This pattern is used throughout the JDK in utility methods like Collections.swap and Collections.fill.

At National Bank of India, Rajesh's utility library had a method that accepted List of account objects and needed to shuffle them. The shuffle required set() operations, which the wildcard blocked. By introducing a private capture helper, the method could safely swap elements while keeping the public API flexible enough to accept any List regardless of element type.

Remember one sentence: List is read-only because the compiler does not know the element type, and the capture helper pattern delegates to a private generic method where the wildcard is captured as a named type T for type-safe operations.

---

Share
Use ← → arrow keys to navigate