Back to Blog
Java

Java Unbounded Wildcard: What List<?> Means

Learn how the Java unbounded wildcard works in generics, when to use `List<?>`, and why adding elements is a compile-time error.

Java genericsWildcardsType safetyType erasureGeneric methods
Diagram showing a Java List<?> with an unknown element type and the compiler blocking an add operation.

What an Unbounded Wildcard Represents

The Java unbounded wildcard is written as ? and appears inside a type argument, such as List<?>. It declares that the element type is unknown, but it is not the same as a raw type like List. A raw List bypasses generic element type checks, while List<?> keeps the type system engaged and refuses operations that cannot be proven safe.

The unbounded wildcard is the simplest of the three wildcard forms. The other two are bounded wildcards: ? extends T for an upper bound and ? super T for a lower bound. In most contexts, an unbounded wildcard behaves like ? extends Object, but the shorter form is preferred because it is clearer and avoids implying that the upper bound is a deliberate choice.

Declaring Unbounded Wildcard Types

The syntax appears wherever a type argument is expected:

List<?> items; Map<String, ?> attributes; Class<?> unknownClass;

A common use is a method parameter that accepts a collection without caring about its element type:

public static void printAll(List<?> items) { for (Object item : items) { System.out.println(item); } }

Because the element type is unknown, every element read from items is treated as Object. That is the only type guaranteed to be a supertype of every possible element type.

Reading Elements from an Unbounded Wildcard Collection

You can read from a List<?> without restriction. Iteration, indexing, and stream operations all work, but the result is always Object:

List<?> data = List.of("a", "b", "c"); Object first = data.get(0);

You cannot assign the result to a more specific type without an explicit cast, and a cast is only safe if you know the runtime type from context. The compiler will not help narrow the type because it has no information about what the list actually contains.

This behavior is useful for operations that only depend on Object methods, such as toString, hashCode, or equals. A method that prints, counts, or checks for emptiness does not need to know the element type.

Why You Cannot Add Elements to a List<?>

The restriction that most developers encounter is the compiler error when writing to an unbounded wildcard collection:

List<?> items = new ArrayList<String>(); items.add("hello"); // compile error

The reason is straightforward. The compiler knows the list contains some unknown type ?. A String might be the right type, or it might not. Because the compiler cannot prove that String is a subtype of the unknown element type, it rejects the call.

The only value you can add directly is null, because null is assignable to every reference type:

items.add(null); // allowed

This asymmetry between reading and writing is the defining characteristic of wildcard types. It is not a limitation to work around; it is the mechanism that keeps the code type-safe.

Unbounded Wildcards vs Bounded Wildcards

The choice between ?, ? extends T, and ? super T depends on whether the code reads, writes, or both:

Wildcard formCan read asCan writeTypical use
?Objectnull onlyOperations independent of element type
? extends TTnull onlyRead-only access to a specific upper bound
? super TObjectT and subtypesWrite-oriented operations like Collections.addAll

Use an unbounded wildcard when the operation does not depend on the element type at all. Use ? extends T when you need to treat elements as T while reading. Use ? super T when you need to insert values of type T.

A method that only prints elements should use List<?>. A method that needs to call a method defined on T should use List<? extends T>.

Where Unbounded Wildcards Appear in the Standard Library

The Java standard library uses unbounded wildcards in several places. Collections.max and Collections.min accept a Collection<? extends T> because they need to compare elements. Collection.containsAll accepts a Collection<?> because it only needs to check membership. Class<?> appears in reflective code where the class type is not known at compile time.

These signatures reveal a pattern: if a method only reads from a collection and the result does not expose the element type, an unbounded wildcard is often the right parameter type.

Runtime Behavior and Type Erasure

Wildcards are a compile-time construct. After type erasure, List<?> and List<String> both become the raw List type at runtime. The JVM has no knowledge of wildcards, and no runtime checks are performed for wildcard boundaries.

This means an unbounded wildcard adds no runtime cost. There is no hidden wrapper object, no extra indirection, and no reflection involved. The only effect is at the compiler level, where the wildcard restricts what operations are allowed.

Erasure also affects instanceof. Only reifiable types can be tested at runtime, and List<?> is reifiable while List<String> is not:

if (list instanceof List<?>) { // valid } if (list instanceof List<String>) { // compile error }

A parameterized instanceof check compiles only when the type argument is an unbounded wildcard, such as List<?>, because the unbounded wildcard erases to the raw type without imposing a runtime type constraint.

Common Mistakes and Edge Cases

The most frequent mistake is attempting to add an element to a List<?> and then working around the compiler error with an unchecked cast. The cast only silences the compiler; because erasure removes the runtime type argument, it cannot make the operation safe. It can corrupt a list and typically leads to a ClassCastException later, when a value is read through the wrong type.

Another mistake is confusing List<?> with List<Object>. They are not interchangeable. List<Object> accepts any element because Object is the declared type. List<?> accepts no element except null because the declared type is unknown. A method parameter typed as List<Object> cannot accept a List<String>, but a parameter typed as List<?> can.

Null handling deserves attention. Because null is the only value that can be added to an unbounded wildcard collection, code that relies on the absence of nulls must check explicitly. A List<?> may contain nulls, and reading them produces a null reference that must be handled downstream.

Maintainability Considerations

Unbounded wildcards improve maintainability by making method contracts explicit. A parameter typed as List<?> tells the caller that the method cannot depend on the element type and cannot add elements of an unknown type. It does not promise immutability: clear and remove are still allowed. This restriction is a form of documentation enforced by the compiler.

The tradeoff is that the method cannot express any relationship between the element type and the return type. If a method reads a list and returns one of its elements, the return type must be Object, which forces callers to cast. In that case a generic method with a type parameter is a better choice:

public static <T> T firstElement(List<T> items) { return items.get(0); }

Use an unbounded wildcard when the element type is irrelevant. Use a type parameter when the element type must be preserved through the method signature.

Java Unbounded Wildcard: Practical Usage and Code Examples | RYUSLOG DEV