skip to content

What does the unbounded wildcard List<?> mean, and how is it different from List<Object> and the raw type List?

level: middleimportance: must knowfreq 70%

answer

  1. ? = some unknown specific type
  2. Supertype of every List<X>
  3. Read as Object, add only null
  4. Not the same as List<Object> (invariance)
  5. Type-safe; raw type is not

basics

~20 s

List<?> means 'a list of some unknown but specific type'. List<Object> is a list that explicitly holds any object, and raw List has no type info at all. List<?> is type-safe; raw List is not.

solid answer

~40 s

List<?> is the unbounded wildcard: 'a List of some unknown type'. The element type exists but is unknown to the compiler at that point. This differs from List<Object>, which is a concrete parameterization where elements are declared to be Object - you can add any object to it. It also differs from the raw List, a pre-generics escape hatch that turns off generic type checking entirely and produces unchecked warnings. Crucially List<String> IS a List<?> but is NOT a List<Object> (generics are invariant), so List<?> lets a method accept lists of any element type without losing compile-time safety. With List<?> you can read elements as Object and you cannot add anything except null, because the compiler cannot prove what the unknown type is.

go deeper

for a junior

Knows List<?> means 'a list of some unknown type' and that you read elements as Object.

for a middle

Distinguishes List<?> from List<Object> and the raw type, and explains the add-only-null restriction.

for a senior

Grounds the difference in invariance and explains why each form behaves as it does, including when to choose the wildcard.

for a principal

Can articulate the type-system design tradeoff (invariance for soundness) and guide API design toward wildcards for read-only flexibility.

## What a generic type is In Java, a *generic* type like `List<E>` is parameterized: `E` is a placeholder filled in when you use it, e.g. `List<String>` or `List<Integer>`. The thing in the angle brackets is the *type argument*. ## The problem wildcards solve: invariance Generics in Java are **invariant**. That means `List<String>` is **not** a subtype of `List<Object>`, even though `String` is a subtype of `Object`. If it were allowed, you could do this and break the heap: ``` List<String> ls = ...; List<Object> lo = ls; // illegal in Java for a reason lo.add(42); // would put an Integer into a String list! String s = ls.get(0); // ClassCastException at runtime ``` So `void printAll(List<Object> list)` can only accept a `List<Object>` - not a `List<String>`. That is too restrictive when you only want to *read*. ## The unbounded wildcard `?` The **unbounded wildcard** is written `List<?>` and is read as "a list of **some unknown type**". The `?` stands for one specific-but-unknown type. `List<?>` is the **supertype of every parameterization of List**: `List<String>`, `List<Integer>`, `List<List<Date>>` - all are `List<?>`. So a method declared `void printAll(List<?> list)` accepts a list of *anything*. ## How it differs from the alternatives - **`List<Object>`** - a *concrete* parameterization. The element type is known: `Object`. You can `add` any object to it. But because of invariance, a `List<String>` cannot be passed where a `List<Object>` is expected. - **Raw `List`** - the pre-generics (Java 4 and earlier) form. Using it **disables generic type checking** for that reference; the compiler emits *unchecked* warnings and you lose safety. It exists only for backward compatibility. `List<?>` keeps full type safety while still accepting any element type. - **`List<?>`** - the wildcard. Type-safe, accepts any parameterization, but **read-only** for elements (you get back `Object`) and you **cannot add** anything except `null`. ## Why you can't add to a `List<?>` The compiler does not know what `?` actually is. If it's `List<String>`, adding an `Integer` would corrupt it; if it's `List<Integer>`, adding a `String` would. Since the compiler cannot prove your value matches the unknown element type, it forbids `add(x)` for every `x` - **except `null`**, which is a valid value of every reference type. You *can* read, but only as `Object`, because every type is an `Object`. ## When to use it Use `List<?>` for methods that only need to operate on a list *structurally* without caring about (or touching) the element type: e.g. `boolean isEmpty(List<?> l)`, `int size(List<?> l)`, or printing. It is the cleanest way to say "I take a list of anything and I won't modify its contents."

  • Why can List<String> be assigned to List<?> but not to List<Object>?
    Because List<?> is the supertype of all List parameterizations, so any List is a List<?>. List<Object> is a concrete parameterization and generics are invariant, so List<String> is not a subtype of List<Object>.
  • What is the one thing you can add to a List<?>?
    null - it is a valid value of every reference type, so it is always type-safe to add.

saying these in an interview costs you the question

  • Saying List<?> equals List<Object> - it does not; List<String> is a List<?> but not a List<Object>
  • Claiming List<?> is the same as the raw type List - the raw type disables type checking, the wildcard keeps it
  • Thinking you can add any element to a List<?> (only null is allowed)
  • Saying ? means 'any type at once' - it means one specific but unknown type

context