How do raw types cause heap pollution, and why does the compiler allow code that mixes raw and parameterized types?
answer
- Raw type = generic used without <...>
- Raw reference disables element type checks
- Allowed for backward compat with pre-Java-5 code
- Compiler warns 'unchecked', does not error
- Failure = delayed ClassCastException at read
basics
~20 sA raw type like plain List turns off generic checking, so you can put any object into it. If that same object is also viewed as a List<String>, you have heap pollution. Java allows the mix only so old pre-generics code still compiles.
solid answer
~50 sA raw type is a generic class used without type arguments, such as List instead of List<String>. Using a raw reference disables the compiler's generic type checks for that reference, so you can add any object to it. If the underlying object is also reachable through a parameterized reference like List<String>, the parameterized view now lies about its contents — that is heap pollution. The compiler permits assigning between raw and parameterized types, but it emits an unchecked warning to flag the loss of type safety. This leniency exists for backward compatibility: when generics were added in Java 5, vast amounts of existing code used raw collections, and that code had to keep compiling and interoperating with new generic code. The cost is that mixing the two can silently pollute the heap and produce a ClassCastException later, at a read site that looks innocent.
code
java · 8 linesList<String> strings = new ArrayList<>();
List raw = strings; // unchecked warning: raw type
raw.add(Integer.valueOf(42)); // no check, no immediate error -> heap pollution
String first = strings.get(0); // ClassCastException thrown here (hidden (String) cast)
// Safe alternative when the element type is unknown:
List<?> unknown = strings; // cannot add arbitrary elements, so cannot pollutego deeper
Knows that a raw type is a generic without its type argument, that it disables element checks, and that mixing it with a parameterized type can cause a ClassCastException later.
Explains erasure as why the bad add is not caught, identifies the unchecked warning, and recommends List<?> over a raw type.
Articulates the backward-compatibility rationale, the warn-not-error policy, and strategies to confine raw-API interaction safely.
Sets codebase policy banning raw types, frames the erasure/compatibility trade-off, and guides safe interop boundaries with legacy raw APIs.
## Key terms - **Generic type**: a class/interface parameterized by a type, e.g. `List<E>`. When you write `List<String>`, `String` is the **type argument**. - **Raw type**: the same generic type used **without** any type argument — just `List`. It is the pre-generics form of the type. - **Type erasure**: at runtime, Java discards the type arguments. `List<String>` and the raw `List` are the *same* class at runtime; the `<String>` exists only at compile time. - **Unchecked warning**: a compiler warning that says "I cannot verify this operation is type-safe." - **Heap pollution**: a parameterized reference (e.g. `List<String>`) that actually refers to an object holding the wrong types. ## Why raw types exist Generics arrived in Java 5 (2004). Before that, all collections were raw: `List list = new ArrayList(); list.add("x");`. Millions of lines of such code existed. To avoid breaking it, the designers made generics **erased** and kept **raw types legal**, so old code compiles unchanged and old and new code interoperate. A raw reference simply opts out of generic checking. ## How a raw type pollutes the heap Because a raw reference performs no generic checks, you can add anything to it. If the same object is also viewed through a parameterized reference, the two views disagree: ```java List<String> strings = new ArrayList<>(); List raw = strings; // widen to raw type — compiler gives an unchecked warning raw.add(Integer.valueOf(42)); // allowed: raw type does no element check // 'strings' now "contains" an Integer, though its type says List<String> String s = strings.get(0); // ClassCastException thrown HERE ``` Nothing fails at `raw.add(42)` because at runtime both references point to the same plain `ArrayList`, and an `Integer` is a perfectly valid `Object`. The pollution is silent. The failure appears at `strings.get(0)`: because `strings` is typed `List<String>`, the compiler inserted a hidden `(String)` cast on the result, and that cast meets the `Integer` and throws. The error site (a normal read) is far from the cause (the raw add). ## Why the compiler only warns, not errors Making raw-to-parameterized assignment an *error* would break the backward-compatibility goal. So the compiler permits it but issues an **unchecked warning** to draw your attention to the lost safety. In modern code, you should treat these warnings as bugs: avoid raw types entirely, and if you must interact with a raw API, confine and validate the values before exposing them through a parameterized reference. ## Takeaways - Raw types switch off generic checking and are the most common everyday cause of heap pollution. - They are legal only for backward compatibility with pre-Java-5 code. - The compiler warns (unchecked) rather than errors, and the runtime failure is a delayed `ClassCastException` at a read site. - Rule of thumb: never introduce raw types in new code; use `List<String>` (or `List<?>` when the element type is genuinely unknown).
- What should you use instead of a raw type when you genuinely do not know the element type?Use a wildcard, List<?>. Unlike a raw type, List<?> still enforces safety — you cannot add arbitrary elements to it (only null), so it cannot be used to pollute the heap.
- Why doesn't `raw.add(42)` throw immediately?Because of erasure, at runtime the reference is just an ArrayList with no element-type info, and an Integer is a valid Object. The check that would catch it (the cast) only runs when you read through the List<String> view.
A raw type is like a security checkpoint that has been switched off: anyone (any object) walks through. The problem only shows up later when someone downstream assumes everyone was screened.
saying these in an interview costs you the question
- Confusing a raw type (List) with a wildcard type (List<?>) — the wildcard is safe
- Thinking raw types are forbidden by the compiler
- Believing the bad add() throws an exception
- Saying raw types exist for performance reasons rather than backward compatibility