skip to content

What is a raw type in Java, and why does the language even allow you to write `List` without type arguments?

level: juniorimportance: must knowfreq 70%

answer

  1. Generic type with no `<...>`
  2. Exists for pre-Java-5 backward compatibility
  3. Not the same as `List<Object>` or `List<?>`
  4. Turns off generic checking → unchecked warning
  5. Migration bridge, not for new code

basics

~10 s

A raw type is a generic class or interface used without its type arguments, like writing List instead of List<String>. Java allows it mainly so old pre-generics code (before Java 5) still compiles.

solid answer

~40 s

A raw type is the name of a generic type used with no type arguments, e.g. `List` rather than `List<String>`. When generics were added in Java 5, billions of lines of existing code already used `List` plainly. To stay backward compatible without forcing everyone to rewrite, the language kept raw types legal: `List` is still a valid type that means "a List of unknown, unchecked element type." Using one switches off the compiler's generic type checking for that variable, so the compiler emits an unchecked warning. You essentially fall back to the pre-generics behaviour where everything is treated as `Object` and casts can fail at runtime. New code should always parameterize (`List<String>` or `List<?>`); raw types exist for compatibility, not for everyday use.

go deeper

for a junior

Can define a raw type as a generic used without <...> and knows it produces a warning and is discouraged.

for a middle

Explains the backward-compatibility motivation and that raw List differs from both List<Object> and List<?>.

for a senior

Connects raw types to type erasure and articulates the concrete safety loss (deferred ClassCastException) and migration-bridge intent.

for a principal

Can reason about the language-design tradeoff: keeping raw types legal to allow incremental generic migration of large codebases, and the resulting soundness compromise the compiler manages via unchecked warnings.

## First, what is a generic type? A **generic type** is a class or interface that declares one or more **type parameters** — placeholders for a type you fill in later. `java.util.List<E>` declares the type parameter `E`. When you write `List<String>`, you supply `String` as the **type argument**, producing a **parameterized type**: a List whose elements are Strings. The compiler then enforces that you only add Strings and lets you read Strings out without casting. ## What a raw type is A **raw type** is the name of a generic type used **with no angle brackets at all** — for example `List`, `Map`, `Comparable`, or `MyBox` for a `class MyBox<T>`. It is *not* the same as `List<Object>` and *not* the same as `List<?>`; it is the generic type stripped of its generic information entirely. ```java List<String> typed = new ArrayList<>(); // parameterized List raw = new ArrayList(); // raw type ``` ## Why the language allows them: backward compatibility Generics arrived in **Java 5 (2004)**. Before that, the only way to write a list was `List` (holding `Object`s), and a huge amount of code already did. The designers had a hard constraint: **old `.class` files and old source must keep working** with the new compiler and JVM. If `List` had suddenly become illegal, every existing program would break. The solution was twofold: 1. **Type erasure** — at compile time the generic information is removed, so `List<String>` and `List` compile down to the *same* bytecode (`List`). The JVM never sees the type argument. This is how new generic code can interoperate with old libraries. 2. **Raw types stay legal** — `List` remains a usable type meaning "a List of some unchecked element type." This lets new generic APIs be called from, and pass values to, legacy non-generic code. So raw types are a **migration bridge**, deliberately kept for compatibility — not a feature you are meant to reach for in new code. ## What you lose by using a raw type When you use `List` raw, the compiler turns off generic type checking for that reference. It will let you add *anything*, and reads come back as `Object`. The protections generics were created to provide vanish, so mistakes that would have been compile errors instead blow up at runtime as `ClassCastException`. Because of this, the compiler emits an **unchecked warning** whenever you perform an operation on a raw type that it cannot prove is type-safe. ## The takeaway - Raw type = generic type with the `<...>` dropped. - It exists **only** for pre-generics backward compatibility. - Using one silences generic checking and reintroduces the runtime-cast risks generics were designed to remove. - In new code, always write `List<String>` (a concrete element type) or `List<?>` (an unknown but *checked* element type) instead of raw `List`.

  • Is `List` the same as `List<Object>`?
    No. `List<Object>` is a fully parameterized, type-checked type. Raw `List` opts out of generic checking entirely and triggers unchecked warnings. You can assign `List<String>` to raw `List`, but assigning it to `List<Object>` is a compile error.
  • What is the connection between raw types and type erasure?
    Erasure removes generic info at compile time so `List<String>` and `List` share the same bytecode (`List`). That shared erasure is exactly what makes raw types able to interoperate with both generic and legacy code.

saying these in an interview costs you the question

  • Saying raw `List` is identical to `List<Object>`
  • Saying raw `List` is the same as `List<?>`
  • Thinking raw types were added as a convenience feature rather than for legacy compatibility
  • Claiming using a raw type is just a style preference with no safety cost

context