Question 19 · Section 20

What are raw types and why should you avoid them

A raw type is a generic class without type arguments:

Language versions: English Russian Ukrainian

🟢 Junior Level

Raw type — using a generic class without specifying a type.

// ✅ Parameterized type
List<String> list = new ArrayList<>();
list.add("Hello");
String s = list.get(0);  // no cast needed

// ❌ Raw type — no type
List list = new ArrayList();  // raw type!
list.add("Hello");
list.add(42);  // compiler doesn't complain, but this is a bug!
String s = (String) list.get(0);  // cast required
String s2 = (String) list.get(1); // ClassCastException!

Why to avoid:

  1. ❌ No compile-time type checking
  2. ❌ Casts required
  3. ❌ Risk of ClassCastException at runtime

🟡 Middle Level

What is a raw type

A raw type is a generic class without type arguments:

// Generic class
public class Box<T> {
    private T value;
    public void set(T value) { this.value = value; }
    public T get() { return value; }
}

// Parameterized usage
Box<String> stringBox = new Box<>();  // ✅

// Raw type
Box rawBox = new Box();  // ❌ warning: raw type

What happens:

Box rawBox = new Box();
rawBox.set("Hello");
rawBox.set(42);  // compiler doesn't complain!

// After type erasure:
Box rawBox = new Box();
rawBox.set("Hello");
rawBox.set(Integer.valueOf(42));  // everything is stored

// On read — ClassCastException
String s = (String) rawBox.get();  // OK
Integer i = (Integer) rawBox.get();  // OK
String s2 = (String) rawBox.get();  // ClassCastException!

Common mistakes

  1. Legacy code compatibility: ```java // Pre-Java 5 code List list = new ArrayList(); list.add(“Hello”);

// New code List strings = list; // warning: unchecked conversion


2. **Mixing raw and parameterized:**
```java
Map<String, List<String>> map = new HashMap<>();
Map rawMap = map;  // raw type — type safety lost
rawMap.put("key", 42);  // compiler doesn't complain

// Later — ClassCastException
List<String> list = map.get("key");  // ClassCastException!

Practical usage

The only acceptable raw type usage — getClass():

// getClass() returns Class<? extends |X|>, not raw type.
// Assigning to Class — unchecked conversion, not intended design.
Class<?> clazz = "Hello".getClass();
Class rawClass = "Hello".getClass();  // also OK, but unchecked conversion

// But wildcard is better
Class<?> better = "Hello".getClass();

When raw types are acceptable

  • Compatibility with pre-Java 5 libraries
  • Certain reflection scenarios
  • Class literals: Class.class (though Class<?> is better)

🔴 Senior Level

Internal Implementation

Type erasure with raw types:

// Raw type — all type checks disabled
List<String> typed = new ArrayList<>();
List raw = typed;

raw.add(42);  // compiler allows!
raw.add(true); // compiler allows!

// But type erasure stores data as Object
// raw = typed after erasure — the same object

Unchecked warnings:

// Compiler gives warnings:
// warning: [unchecked] unchecked call to add(E) as a member of raw type List
raw.add(42);

// warning: [unchecked] unchecked conversion
List<String> list = raw;

// warning: [rawtypes] raw type
Box box = new Box();

Architectural Trade-offs

Raw types vs parameterized:

Raw types Parameterized
No type checking Compile-time check
Risk of ClassCastException Type-safe
Legacy compatibility Modern code
No cast on access No cast needed

Edge Cases

1. Raw type in inheritance:

public class Node<T> {
    private T data;
    public T getData() { return data; }
}

public class RawNode extends Node {  // raw type!
    @Override
    public Object getData() { return super.getData(); }
}

// After erasure — bridge methods work correctly
// But type safety is lost

2. Raw type and generic methods:

public class Util {
    public static <T> T identity(T t) { return t; }
}

// Call via raw type
Util u = new Util();  // raw type
Object result = u.identity("Hello");  // T inferred as Object

// Correct
Object result2 = Util.identity("Hello");  // T = String

3. Raw type in arrays:

// Arrays of generic classes — raw type
Box<String>[] boxes = new Box[10];  // warning: raw type

// Better
List<Box<String>> boxes = new ArrayList<>();

Performance

Raw types vs parameterized:
- Runtime: the same (type erasure)
- Compile time: raw types faster (no checking)
- Safety: parameterized significantly safer

Performance is the same, but raw types are risky

Production Experience

Legacy migration:

// Before Java 5
public class OldDao {
    public List findAll() {
        return jdbcTemplate.query("SELECT * FROM users", ...);
    }
}

// After migration
public class NewDao {
    public List<User> findAll() {
        return jdbcTemplate.query("SELECT * FROM users", ...);
    }
}

// Transitional period — raw type warnings
List users = oldDao.findAll();  // raw type
List<User> typedUsers = newDao.findAll();  // parameterized

@SuppressWarnings(“rawtypes”):

// Only when unavoidable
@SuppressWarnings("rawtypes")
public void legacyInterop() {
    List rawList = legacyMethod();
    // ...
}

// ❌ Don't suppress warnings without reason
// ✅ Fix the root cause

Best Practices

// ✅ Always use type arguments
List<String> list = new ArrayList<>();
Map<String, Integer> map = new HashMap<>();

// ✅ Diamond operator (Java 7+)
List<String> list = new ArrayList<>();  // not new ArrayList<String>()

// ✅ Wildcards when type is unknown
public void process(List<?> list) { }

// ⚠️ Raw types only for legacy code
// ⚠️ Suppress warnings only when unavoidable

// ❌ Raw types in new code
// ❌ Mixing raw and parameterized
// ❌ Ignoring unchecked warnings

🎯 Interview Cheat Sheet

Must know:

  • Raw type — using a generic class without a type argument: List list = new ArrayList()
  • Raw types disable compile-time type checking — risk of ClassCastException at runtime
  • Mixing raw and parameterized types infects the entire chain with unchecked warnings
  • The only acceptable raw type — legacy code and certain reflection scenarios
  • After type erasure, raw and parameterized — the same bytecode
  • @SuppressWarnings("rawtypes") — only when unavoidable, not for suppressing without reason

Frequent follow-up questions:

  • When are raw types acceptable? — Pre-Java 5 legacy libraries, some reflection scenarios
  • What happens when mixing raw and parameterized? — Unchecked warnings, loss of type safety
  • Raw type vs wildcard <?>? — Raw disables all checks, wildcard preserves checks
  • Can you create an array of generic classes? — Yes, but it will be a raw type: new Box[10]

Red flags (DO NOT say):

  • ❌ “Raw types are faster than parameterized” — Runtime performance is the same
  • ❌ “Raw types are normal practice in new code” — Always use type arguments
  • ❌ “Raw type is the same as <?>” — Raw disables checks, wildcard preserves them
  • ❌ “SuppressWarnings solves the problem” — The warning remains, you need to fix the root cause

Related topics:

  • [[11. What are Generics in Java]]
  • [[12. What are the advantages of using Generics]]
  • [[13. What is type erasure]]
  • [[21. What is the difference between List<?> and List]]