Raw types in Java
Legacy compatibility, unchecked warnings, heap pollution.
Generics with the label ripped off
A raw type is a generic type used without type arguments: List instead of List<String>. They exist only for compatibility with code written before Java 5. Through a raw type, the compiler stops checking and just emits unchecked warnings.
List raw = new ArrayList<String>();
raw.add(1); // compiles: unchecked warningRaw ≠ List<Object> ≠ List<?>
Three very different things. **List<Object>: checked, holds any Object. List<?>: checked, some unknown type, read-only-ish. List (raw): no checking at all**.
List<Object> a; // checked, accepts Objects
List<?> b; // checked, unknown type
List c; // unchecked: anything goesYour turn
What happens when this runs?
List<String> strs = new ArrayList<>();
List raw = strs;
raw.add(7);
System.out.println(strs.size());
String s = strs.get(0);Compile error at raw.add(7)Prints 1, then throws ClassCastExceptionPrints 1 and ends normally
Show the answer
The raw alias lets an Integer into a List<String>, and nothing complains (size prints 1). The crash comes at strs.get(0), where the compiler-inserted cast to String fails.
Heap pollution
That's heap pollution: a variable of a generic type pointing at an object holding the wrong type. Erasure means the list itself never checks, so the bomb only goes off when someone reads the element, often far from the bad write.
Raw erases EVERYTHING
Use a class raw and all generics of its instance members are erased, even ones unrelated to T. Here names() returns a raw List, so get(0) gives Object and the assignment doesn't compile.
class Holder<T> {
List<String> names() {
return List.of("a");
}
}
Holder raw = new Holder();
String s = raw.names().get(0); // ✗ ObjectWhen you don't know the type
static void dump(List items) {
items.add("oops"); // allowed!
}No checks at all: anything can be added to any list passed in.
static void dump(List<?> items) {
for (Object o : items) IO.println(o);
}Still accepts every list, but the compiler blocks unsafe adds.
In legacy code
Old libraries and pre-2004 code are full of raw types. Compile with -Xlint:unchecked to see every risky spot. If you must silence a warning, put @SuppressWarnings("unchecked") on the smallest possible scope, ideally one variable, after convincing yourself it's safe.
Key takeaways
- List (raw) ≠ List<Object> ≠ List<?>
- Raw types silence type checks and give unchecked warnings
- Heap pollution: wrong-typed element hidden inside a typed list
- Using a raw type erases generics of ALL its instance members
The term "heap pollution" comes straight from the Java Language Specification (section 4.12.2), which describes exactly this: a variable of a parameterized type referring to an object that isn't of that type.
Practice questions
Why does Java still allow raw types?
- They are faster than generic types
- For compatibility with code written before generics existed (Java 5)
- They are needed to store primitives
- They're required for arrays
Check your answer
For compatibility with code written before generics existed (Java 5). Generics were added in Java 5 using erasure precisely so old, non-generic code could keep compiling and interoperating.
Does this compile?
List raw = new ArrayList<String>();
raw.add(1);- No — type mismatch on add
- Yes, with an unchecked warning
- Yes, and add(1) throws at runtime
- No — raw types were removed in Java 9
Check your answer
Yes, with an unchecked warning. Through a raw reference the compiler can't check the element type, so it just warns about an unchecked call. At runtime the Integer goes in without complaint.