skip to content

Explain the rules for assigning between parameterized types and raw types in both directions, and why they differ.

level: seniorimportance: should knowfreq 48%

answer

  1. Parameterized→raw: safe, no warning (widening)
  2. Raw→parameterized: compiles + unchecked warning (narrowing)
  3. Asymmetry = drop info safe, add unverified info unsafe
  4. `List<String>`→`List<Object>` is an ERROR (invariance), not warning
  5. Raw ≠ `List<?>`: wildcard is checked, raw is not

basics

~20 s

You can assign a parameterized type (like List<String>) to a raw type (List) freely — no warning. Going the other way, raw List to List<String>, compiles but gives an unchecked warning because the compiler can't verify the element type.

solid answer

~50 s

Both directions compile, but they differ in safety. Parameterized → raw (`List raw = stringList;`) is allowed with **no warning**: you're widening to a less-informed type, discarding the element-type guarantee, which is always safe. Raw → parameterized (`List<String> s = raw;`) also compiles but emits an **unchecked conversion warning**, because the compiler is being asked to *trust* that the raw list holds only Strings — it has no way to verify that after erasure, so a later read could throw `ClassCastException`. The asymmetry mirrors the general subtyping rule: discarding type information is safe; reintroducing an unverified type claim is not. Once you have the raw reference, the compiler also lets you call any method on it as if it were `Object`-typed, so `add` calls on it warn too. The clean alternatives are full parameterization or a wildcard `List<?>` when the element type is truly unknown.

go deeper

for a junior

Knows you can put List<String> into a List and that the reverse 'feels' riskier and warns.

for a middle

States both assignment directions correctly (no-warning vs unchecked warning) and that both still compile.

for a senior

Explains the widening/narrowing asymmetry, distinguishes the warning (raw→param) from the error (List<String>→List<Object> invariance), and contrasts raw with List<?>.

for a principal

Frames the rules in terms of soundness/variance design choices, the erasure-driven unverifiability, and the codebase-level policy for accepting and isolating unavoidable raw-type boundaries with legacy code.

## Setup: the four directions Given `class List<E>`, there are two relevant types: the **raw** `List` and a **parameterized** `List<String>`. We care about assigning one to a variable of the other, in each direction. ## Direction 1: parameterized → raw (safe, no warning) ```java List<String> stringList = new ArrayList<>(); List raw = stringList; // OK, no warning ``` This is **always permitted with no warning**. You are taking a value that is *known* to be a List-of-String and storing it under a reference that *forgets* the element type. You're throwing information away, not inventing it. Nothing unsafe can result from the assignment itself — the underlying object is unchanged; you've just chosen to look at it through a less-informed lens. (Of course, *using* that raw reference to `add` a non-String would corrupt the list — but that's a separate raw-write warning, not the assignment.) ## Direction 2: raw → parameterized (compiles, unchecked warning) ```java List raw = getLegacyList(); List<String> stringList = raw; // compiles, but UNCHECKED CONVERSION warning ``` This compiles but produces an **unchecked conversion warning**. The compiler is being asked to believe that `raw` contains only Strings. After **type erasure**, the element-type tag doesn't exist at runtime, so the compiler cannot prove the claim and the JVM won't enforce it. If `raw` actually held an `Integer`, you get no error here — instead a `ClassCastException` fires later, at the first point where code reads an element expecting a String. The warning marks exactly this unverifiable leap of faith. ## Why the asymmetry exists The rule reflects a general soundness principle: **widening to a less-specific type is safe; narrowing to a more-specific type requires a check the system can't always perform.** Parameterized → raw widens (drops the constraint). Raw → parameterized narrows (adds an unverified constraint). It parallels reference casting in general — upcasts are silent, downcasts are checked (or, here, *un*checked because erasure removed the data needed to check). ## Important non-relationships (common traps) - **`List<String>` is NOT a subtype of `List<Object>`.** Generics are invariant. So `List<Object> o = stringList;` is a hard compile **error**, not a warning. Raw `List` is the only "supertype-ish" escape hatch that accepts both — which is part of why raw types are tempting and dangerous. - **Raw `List` ≠ `List<?>`.** You can read from a `List<?>` safely (you get `Object`) but you **cannot add** anything (except `null`) — the wildcard is *checked*. Raw `List` lets you add anything *un*checked. So `List<?>` is the safe replacement when the element type is genuinely unknown; raw is not. - **The diamond doesn't make it raw.** `new ArrayList<>()` is inferred-parameterized, not raw. `new ArrayList()` is raw. ## Method calls through a raw reference Once a reference is raw, the compiler erases the *whole* generic API of that reference, even methods whose signatures don't involve the type parameter, treating type-parameter positions as their erasure (usually `Object` or the bound). That's why `raw.add(x)` warns: `add(E)` is seen as `add(Object)` with no element-type guarantee. ## Practical guidance - Prefer never holding a raw reference. If you must accept legacy raw output, convert deliberately and document the safety argument, suppressing the single warning narrowly. - When the element type is unknown but you only read, use `List<?>`. - Remember the error-vs-warning split: parameterized-to-different-parameterized (`List<String>`→`List<Object>`) is an **error**; raw-to-parameterized is a **warning**. ## Summary table | From | To | Result | |---|---|---| | `List<String>` | `List` (raw) | OK, no warning (widening) | | `List` (raw) | `List<String>` | compiles + unchecked warning (unverifiable narrowing) | | `List<String>` | `List<Object>` | compile error (invariance) | | `List<String>` | `List<?>` | OK, no warning (read-only view) |

  • Why is `List<Object> o = stringList;` an error while `List raw = stringList;` is fine?
    Generics are invariant, so `List<String>` is not a subtype of `List<Object>` — a hard error that prevents adding a non-String through `o`. Raw `List` is the deliberate compatibility escape hatch, so the assignment is allowed (you just lose checking).
  • If raw→parameterized only warns, when do you actually pay the price?
    At runtime, when code reads an element and the implicit compiler-inserted cast to the parameterized type fails — a `ClassCastException` that can occur far from the unchecked conversion.

saying these in an interview costs you the question

  • Saying raw→parameterized is a compile error (it's only a warning)
  • Saying `List<String>` can be assigned to `List<Object>` (it can't — invariance)
  • Treating raw `List` and `List<?>` as interchangeable
  • Believing the CCE happens at the assignment line rather than at a later read

context