skip to content

Why can you only read elements as `Object` from a `List<? super T>`, and what is the underlying type-safety reasoning?

level: middleimportance: should knowfreq 48%

answer

  1. element type = unknown supertype of T
  2. only Object is common to all supertypes
  3. read precise type → CCE risk → forbidden
  4. contravariant = write-friendly, read-Object
  5. wrong wildcard if you need typed reads

basics

~20 s

Because the list's real element type could be T or any of its parents, the compiler doesn't know the exact type. The only type guaranteed to fit every possibility is Object, so reads come back as Object.

solid answer

~40 s

`List<? super T>` means the actual element type is some unknown supertype of T — it could be T, a parent, a grandparent, up to Object. When you read an element, the compiler must hand you a type that is correct *regardless of* which supertype it really is. The narrowest type that every Java reference is guaranteed to be assignable to is `Object`. So a `get()` on a `<? super T>` reference is typed as Object; assigning it to anything more specific (even T) is a compile error, because the runtime type might be a more general supertype. This is the deliberate cost of the contravariant, write-oriented wildcard: it trades read precision for write flexibility, which is exactly right for a consumer/sink role.

go deeper

for a junior

Knows that reads from <? super T> give Object and not T.

for a middle

Explains the soundness reasoning: the element type is an unknown supertype, so only Object is guaranteed.

for a senior

Frames it as contravariance, contrasts with <? extends T> reads, and chooses the wildcard by read/write need.

for a principal

Connects the rule to type-system soundness/erasure, and can explain why the alternative would reintroduce unchecked-cast unsoundness.

## Setup Consider `List<? super Integer>`. The `? super Integer` part is a **lower-bounded wildcard**: the real element type is **Integer or any supertype of Integer**. Concretely the list could actually be: - `List<Integer>` - `List<Number>` - `List<Comparable<Integer>>` (an interface Integer implements... actually Integer's supertypes include Number, Comparable, Serializable, Object) - `List<Object>` The compiler does **not** know which one — only that it is *somewhere at or above Integer* in the hierarchy. ## The read question When you call `get(i)`, the compiler must assign a static type to the result that is **sound for every possibility** above. Ask: what type are the elements *guaranteed* to be? - If the list is `List<Integer>`, elements are Integers. - If it is `List<Number>`, elements are Numbers (might be a Double!). - If it is `List<Object>`, elements are arbitrary Objects (might be a String!). The **only** type that holds across *all* of these is `Object`. So: ```java List<? super Integer> list = ...; Object o = list.get(0); // OK — always sound Integer i = list.get(0); // COMPILE ERROR — the element might be a Double or a String ``` If the compiler let you read into `Integer`, and the underlying list were a `List<Object>` containing a `String`, you would get a `ClassCastException` at runtime with no cast in your source — violating generics' core promise of compile-time type safety. ## Contrast with the write side Writes are the mirror image. Adding an `Integer` (or subtype) is always safe, because whatever the real element type is, it is a supertype of Integer, and an Integer **is-a** that supertype: ```java list.add(99); // OK Object result = list.get(0); // read back only as Object ``` So a `<? super T>` reference is **good at writing, weak at reading** — the textbook **consumer/sink**. ## The deeper principle: contravariance `<? super T>` makes the type **contravariant** in T: it flips the usual subtype direction for *inputs*. Contravariant positions are exactly the positions you can safely *put values into* but not *pull typed values out of*. The `get → Object` limitation is not an arbitrary rule; it is forced by soundness. ## Practical takeaway If your code needs to **read typed T values**, you have chosen the wrong wildcard — switch to `<? extends T>` (covariant, producer). Use `<? super T>` only where the collection is a destination you write into. This is the read half of the PECS rule.

  • If you genuinely need to read elements as T, which wildcard should you use instead?
    `<? extends T>` — the upper-bounded, covariant wildcard. Reads return T; the trade-off is you can no longer add elements (except null).
  • What runtime error would occur if the compiler wrongly allowed `Integer i = list.get(0)` on a `List<? super Integer>` backed by a `List<Object>` holding a String?
    A ClassCastException at the implicit cast, defeating the compile-time safety guarantee of generics.

saying these in an interview costs you the question

  • Expecting `get()` to return T from a `<? super T>` list.
  • Thinking the restriction is arbitrary rather than forced by soundness.
  • Believing you can cast the Object result safely without runtime risk.
  • Confusing the read behavior with `<? extends T>`, which returns T.

context