Why is a `Collection<? extends T>` effectively read-only for additions, and what is the one value you can still add?
answer
- wildcard capture → CAP#1, unknown subtype
- add rejected: can't prove arg is CAP#1
- only null is addable
- reads safe (get returns T)
- clear/remove-by-index still work
basics
~20 sBecause the real element type behind the wildcard is unknown, the compiler can't guarantee any value you add fits, so it blocks all add calls. The one exception is null, which is valid for every type.
solid answer
~40 s`Collection<? extends T>` says the element type is some unknown subtype of `T` — the *capture* type. When you call `add(x)`, the compiler must check `x` against that unknown type. Since it can't know whether the collection is really `List<T>`, `List<SubA>`, or `List<SubB>`, it conservatively refuses every `add` of a typed value: passing the wrong subtype would corrupt the collection. The compiler reports this as the captured wildcard type `CAP#1`. The single legal addition is `null`, because `null` is assignable to every reference type regardless of the capture. Reads, by contrast, are fine: anything you pull out is guaranteed to be a `T`. This is exactly why `<? extends T>` is the *producer* side of PECS — you read (produce) values of `T`, you don't write them.
go deeper
States that you can't add to a ? extends collection except null, and that you can read from it.
Explains the unknown capture type and why adding any typed value is unsafe; knows null is the exception and reads are fine.
Discusses wildcard capture (CAP#1), heap pollution, which non-element methods still work, and the capture-helper workaround for set.
Connects to type-system soundness, the design choice of conservative rejection, and how capture interacts with inference and generic helper methods in library APIs.
## The setup An upper-bounded wildcard `Collection<? extends T>` means "a collection of some single, fixed-but-unknown type that is `T` or a subtype of `T`." The key words are *single*, *fixed*, and *unknown*. It is one concrete type at runtime — you just don't get to know which one at compile time. ## Wildcard capture When the compiler type-checks code that uses a wildcard, it performs **wildcard capture**: it invents a fresh, unnameable type variable — shown in error messages as something like `CAP#1` — standing for "the actual unknown type here, which is `≤ T`." Everything the compiler knows is: `CAP#1 extends T`. It does NOT know the exact identity of `CAP#1`. ## Why writes are rejected Consider: ```java void corrupt(List<? extends Number> list) { list.add(Integer.valueOf(1)); // does NOT compile } ``` The `add` method's signature is effectively `add(CAP#1 e)`. To pass an `Integer`, the compiler needs `Integer` to be a subtype of `CAP#1`. But `CAP#1` could be `Double`, `Long`, `BigDecimal`, or any other subtype of `Number`. There is no value (other than `null`) the compiler can prove is a `CAP#1`. So it rejects the call. This is not a limitation for its own sake — it plugs a real hole. If adds were allowed, you could call `corrupt(new ArrayList<Double>())` and end up with an `Integer` inside a `List<Double>`, which would later blow up with a `ClassCastException` when something reads it as `Double`. ## Why `null` is the exception `null` is a member of every reference type — it is the bottom of the reference type hierarchy. So `null` is provably a `CAP#1` no matter what `CAP#1` turns out to be. Therefore `list.add(null)` compiles. It's the only value you can add, and it's rarely useful, which is why we say the collection is "effectively read-only." ## Why reads still work Reading is the mirror image. `Number n = list.get(0);` works because whatever `CAP#1` is, it is guaranteed to be a `Number` (the upper bound). So you can always read out a value typed as the bound `T`. You just can't read it as anything more specific without a cast. ## Methods that don't depend on the element type still work Note that not *every* mutating method is blocked — only those that take a `T`-typed parameter to insert. You can still call `list.clear()`, `list.remove(0)` (by index), `list.size()`, or iterate, because none of them require producing a value of the unknown type. The block is specifically on "accepting a value of the capture type." ## Tie back to PECS **Producer Extends, Consumer Super.** A `<? extends T>` collection is a *producer*: you read `T`s out. A `<? super T>` collection is a *consumer*: you write `T`s in. Choosing `extends` is a declaration that you intend only to consume the collection's contents, not feed it.
- Can you call `list.set(0, list.get(0))` on a `List<? extends T>`?Not directly — `set` takes a `CAP#1`, and even passing back `get(0)` (typed `T`/Number) isn't provably a `CAP#1`. A generic helper method that captures the wildcard into a type variable is the standard workaround.
- What term describes the bug that would occur if adds were allowed?Heap pollution — a variable of a parameterized type refers to an object that is not of that parameterized type, surfacing later as a ClassCastException.
saying these in an interview costs you the question
- Claiming you literally cannot mutate the collection at all — `clear`, `remove(int)`, iteration still work.
- Saying the restriction is arbitrary rather than preventing heap pollution / ClassCastException.
- Forgetting that `null` is addable.
- Confusing capture (`CAP#1`) with the raw type.