skip to content

When, if ever, is using a raw type actually acceptable or required in modern Java, rather than a parameterized type or wildcard?

level: principalimportance: nice to knowfreq 30%

answer

  1. Default: never use raw in new code
  2. Required-raw: class literal `List.class`
  3. Required-raw: `instanceof List` / `List<?>`
  4. Both forced by erasure (runtime has no type args)
  5. `List<?>` = safe stand-in for raw

basics

~10 s

Almost never in new code. The legitimate cases are narrow: using class literals (List.class) and the instanceof operator, where you must use the raw form. Otherwise prefer List<String> or the wildcard List<?>.

solid answer

~50 s

In ordinary code you should essentially never use a raw type — `List<String>` or the unbounded wildcard `List<?>` covers the cases people reach for raw. There are two places where the raw form is **required by the language**, not a choice: class literals must be raw (`List.class` is legal; `List<String>.class` is not, because there's only one Class object per erasure), and `instanceof` must use the raw type or an unbounded wildcard (`o instanceof List`), since erasure means the runtime can't test `o instanceof List<String>`. Beyond those, interoperating with old non-generic APIs may force you to *touch* raw types at the boundary, but you should immediately convert to a parameterized type, validate, and isolate the single unchecked warning. Effective Java's guidance ("don't use raw types") stands: prefer `List<?>` when the element type is unknown, because it keeps compile-time safety, whereas raw throws it away.

code

java · 14 lines
java
// Required-raw corner 1: class literals (one Class per erasure)
Class<?> c = List.class;          // OK
// Class<?> bad = List<String>.class; // does not compile

// Required-raw corner 2: instanceof (element type is erased)
Object o = getSomething();
if (o instanceof List<?> list) {  // preferred over raw `List`
    for (Object e : list) { /* read as Object, safe */ }
}

// Avoidable: "a list of unknown type" -> use wildcard, not raw
void printSize(List<?> any) {     // safe: cannot add, can read as Object
    System.out.println(any.size());
}

go deeper

for a junior

Knows the rule of thumb 'don't use raw types' and that wildcards exist as the alternative.

for a middle

Can name List<?> as the safe stand-in and recall that instanceof/class literals behave specially.

for a senior

Precisely identifies the two language-required raw forms (class literals, instanceof), prefers List<?> for instanceof, and applies convert-validate-isolate at legacy boundaries.

for a principal

Connects the required-raw corners to the erasure design choice, sets codebase policy (lint-ban raw, allow the two forms, reviewed scoped suppressions), and distinguishes forced-raw from avoidable raw smells when assessing risk.

## The default answer **Don't use raw types.** *Effective Java* (Item: "Don't use raw types") makes this a rule, and it's right: a raw type silently disables generic type checking and reintroduces the runtime-`ClassCastException` risk generics removed. For nearly every motivation people have, there's a safe alternative: - Know the element type? Use `List<String>`. - Element type genuinely unknown, but you want safety? Use the **unbounded wildcard** `List<?>` — you can read elements as `Object` and the compiler stops you from adding anything (except `null`), so you can't corrupt it. This is the safe replacement for "I just need *a* list of *something*." ## The two cases where raw is *required* by the language These are not stylistic — the grammar forces the raw (or wildcard) form because of **type erasure**: **1. Class literals.** A `Class` object exists per erased type, not per parameterization — there is exactly one `Class` for `List`, shared by `List<String>` and `List<Integer>`. So: ```java List.class // legal — the raw class literal List<String>.class // COMPILE ERROR — not expressible String[].class // legal; arrays are reifiable ``` If you need a runtime `Class` token for a generic type, you use the raw class literal (or the `Class<List>` raw-ish form). **2. `instanceof`.** Because the element type is erased, the runtime cannot answer "is this object a List *of String*?". So: ```java if (o instanceof List) { ... } // legal — raw if (o instanceof List<?>) { ... } // legal — unbounded wildcard, equivalent at runtime if (o instanceof List<String>) { ... } // COMPILE ERROR ``` The idiomatic form is `instanceof List<?>` (clearer intent), but you may also see raw `List`. After the test, cast to a wildcard type, not a concrete parameterization, to stay honest about what's verifiable: ```java if (o instanceof List<?> list) { /* use list with Object reads */ } ``` ## Legacy interoperability (a *boundary*, not a license) When calling pre-generics APIs (older libraries, some reflection results), values may arrive as raw types. This *forces contact* with raw types but does **not** justify spreading them. The disciplined pattern is: 1. Receive the raw value at the boundary. 2. Convert/validate into a parameterized type (e.g. copy into a `List<String>`, checking elements if you can't trust the source). 3. Confine the unavoidable `@SuppressWarnings("unchecked")` to the smallest scope with a comment proving safety. ## What raw types are *not* a good answer for - "I want a list that can hold any type" → use `List<Object>` (checked) or `List<?>` (read-only), not raw. - "I want to avoid typing the type argument" → use the diamond `<>` for inference; that's still parameterized. - "I want a common supertype to accept `List<String>` and `List<Integer>`" → use `List<?>`; it accepts both with safety, whereas raw accepts both *without* safety. ## Principal-level framing The existence of these required-raw corners is a direct, observable consequence of the **erasure-for-compatibility** design choice: because the runtime doesn't carry type arguments, the two runtime-facing operations (class literals, `instanceof`) can't see them either. Recognizing *which* raw usages are forced by the language versus which are avoidable smells is part of reading a codebase's risk surface. Policy: ban raw types in new code via static analysis, allow the two language-required forms (prefer the `<?>` variant for `instanceof`), and require reviewed, scoped suppressions at legacy boundaries. ## Summary - New code: never use raw types; use `List<String>` or `List<?>`. - Required-raw: **class literals** (`List.class`) and **`instanceof`** (`o instanceof List`/`List<?>`) — both forced by erasure. - Legacy interop forces *contact*, handled by convert-validate-isolate, not by adopting raw types broadly. - `List<?>` is the safe stand-in for "a list of unknown type"; raw is the unsafe one.

  • Why can't you write `o instanceof List<String>`?
    Type erasure removes the element type at runtime, so the JVM only knows `o` is (or isn't) a `List` — it cannot test 'List of String.' The language therefore only allows raw `List` or the unbounded wildcard `List<?>` in `instanceof`.
  • If raw types are banned, how do you accept any `List` regardless of element type in a method?
    Use `List<?>` — the unbounded wildcard. It accepts `List<String>`, `List<Integer>`, etc., lets you read elements as `Object`, and forbids unsafe additions, preserving compile-time safety that raw `List` would discard.

saying these in an interview costs you the question

  • Claiming `List<String>.class` or `o instanceof List<String>` is valid
  • Recommending raw types to hold 'any type' instead of `List<?>`/`List<Object>`
  • Treating legacy interop as a blanket license to use raw types everywhere
  • Confusing the unbounded wildcard (checked, safe) with the raw type (unchecked, unsafe)

context