skip to content

When should you use an unbounded wildcard `List<?>` instead of `? extends T` or `? super T`, and what can you do with it?

level: seniorimportance: nice to knowfreq 30%

answer

  1. List<?> = list of unknown type
  2. read as Object, add only null
  3. use when element type is irrelevant
  4. prefer over raw List (keeps safety)
  5. List<?> != List<Object>

basics

~20 s

Use List<?> when the method does not care about the element type at all — e.g. it only checks size or clears the list. You can read elements as Object and you cannot add anything except null.

solid answer

~40 s

`List<?>` is the **unbounded wildcard** — "a list of some unknown type." Reach for it when the method genuinely does not depend on the element type: counting elements, checking emptiness, printing via `toString`, or clearing. You can read elements (typed as `Object`) and call type-independent methods, but you cannot add anything except `null`, for the same reason as `? extends`: the element type is unknown. If you actually need to read elements as a specific T, use `? extends T`; if you need to add T's, use `? super T`; if you need both, use a real type parameter `<T>`. Prefer `List<?>` over the raw type `List`, because raw types disable generic checks entirely and are unsafe — the wildcard keeps type-safety while expressing "I don't care which type."

code

java · 9 lines
java
static int countNonNull(List<?> list) { // element type irrelevant
    int n = 0;
    for (Object o : list) if (o != null) n++; // read as Object
    return n;
    // list.add("x"); // would not compile
}

countNonNull(List.of(1, 2, 3));      // List<Integer>
countNonNull(List.of("a", "b"));     // List<String>

go deeper

for a junior

Knows List<?> means an unknown element type and that you mostly just read it as Object.

for a middle

Chooses List<?> when the element type is irrelevant and explains why adds are forbidden.

for a senior

Distinguishes List<?> from raw List and from List<Object>, and prefers the wildcard for safety.

for a principal

Sets conventions banning raw types, and reasons about API ergonomics of unbounded vs bounded wildcards across a codebase.

## What an unbounded wildcard is `List<?>` uses the **unbounded wildcard** `?`, read as "list of some unknown type." It is shorthand for `List<? extends Object>`. It means: there is one specific element type, but this code does not know or care what it is. ## When to use it Choose `List<?>` when the method's logic is independent of the element type. Classic cases: - `printAll(List<?> list)` — iterates and prints each element via `Object.toString()`. - `int size(List<?> list)` — only needs `size()`. - `boolean isEmpty(Collection<?> c)`. - `void clear(List<?> list)` — `clear()` does not involve the element type. If the body must produce typed elements (`T x = list.get(0)` as something more specific than Object) use `? extends T`. If it must consume typed elements (`list.add(t)`) use `? super T`. If it does both, declare a named type variable `<T>`. ## What you can and cannot do - **Read:** allowed; the result type is `Object` (since `?` is some unknown type, only `Object` is guaranteed). - **Add:** forbidden except `null`, exactly like `? extends T` — the compiler cannot prove any concrete value matches the unknown element type. - **Type-independent operations:** `size()`, `clear()`, `remove(Object)`, `contains(Object)`, `iterator()` all work, because they do not take the element type as a parameter. ## `List<?>` vs the raw type `List` This is the key reason the wildcard exists. The **raw type** `List` (no type argument) is a legacy, pre-generics form. Using it silently disables generic type checking: you can add anything and get unchecked-warning-laden, unsafe code. `List<?>` keeps full type safety — it just refuses unsafe operations — while still expressing "any element type." **Always prefer `List<?>` to raw `List`.** ```java void dump(List<?> list) { // safe: can't add junk for (Object o : list) System.out.println(o); } void dumpRaw(List list) { // unsafe: legacy raw type list.add("oops"); // compiles, no error — heap pollution risk } ``` ## `List<?>` vs `List<Object>` They are not the same. `List<Object>` is a list whose element type **is exactly** Object; only a `List<Object>` argument is accepted. `List<?>` accepts a `List` of *any* element type (`List<String>`, `List<Integer>`, ...), but in exchange you cannot add typed elements. Use `List<Object>` when you truly want to store arbitrary objects and mutate; use `List<?>` for read-only, type-agnostic traversal. ## Terms defined - **Unbounded wildcard:** `?` with no bound; equivalent to `? extends Object`. - **Raw type:** a generic type used with no type argument (`List`), retained only for backward compatibility; bypasses generic checks. - **Heap pollution:** placing a wrong-typed element into a parameterized collection, surfacing later as a ClassCastException.

  • What is the difference between `List<?>` and the raw type `List`?
    `List<?>` keeps type safety — it accepts any element type but forbids unsafe adds. Raw `List` disables generic checking entirely, letting you add anything and risking heap pollution. Always prefer the wildcard.
  • Is `List<?>` the same as `List<Object>`?
    No. `List<Object>` requires the element type to be exactly Object and allows adds. `List<?>` accepts any element type but forbids typed adds (only null) and reads come back as Object.

saying these in an interview costs you the question

  • Treating `List<?>` as equivalent to `List<Object>`
  • Thinking you can add elements to `List<?>`
  • Using the raw type `List` instead of `List<?>`
  • Believing reads return something more specific than Object

context