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 abandons compile-time type checking entirely, 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. An unbounded wildcard is equivalent to ? 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 one value that can always be added 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.

Because erasure removes the wildcard, you cannot use instanceof with a parameterized type that includes a wildcard:

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

The List<?> form is the only parameterized instanceof check that compiles, because ? is erased to the raw type.

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 a cast. The cast does not help; the runtime type of the list is still checked, and the operation fails with ClassCastException if the element type does not match.

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 will not modify the list and will not depend on the element type. This 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