Raw Types
A raw type in Java is the use of a generic class or interface without any type argument. Writing List instead of List<String>, or HashMap instead of HashMap<String, Integer>, or using the class name of a generic class as a type without angle brackets in any position — these are all raw types. Raw types exist solely for backward compatibility with pre-Java-5 code that predates generics; all generic type checking is disabled for a raw type and its members, and the compiler emits unchecked warnings wherever raw types interact with parameterized types. Understanding raw types is essential for reading legacy code, understanding the warnings the compiler emits, and grasping exactly what guarantee is lost when a raw type is used. This entry covers how raw types are defined and how they relate to the erased form of a generic type, the precise scope of type checking that is disabled for raw type members, the specific unchecked warnings that arise, the difference between raw types and wildcard types, and the migration compatibility rules that raw types enable.
What Raw Types Are and What Type Safety They Lose
// ── Raw type declarations — what the compiler sees ──────────────────
List rawList = new ArrayList(); // raw type — WARNING: "raw types"
List<String> typedList = new ArrayList<>(); // parameterized — no warning
// ── Member access through a raw type — all generics disabled ─────────
rawList.add("hello"); // accepts anything — no type checking on argument
rawList.add(42); // Integer added to same list — compiler doesn't object
rawList.add(3.14); // Double too — anything goes
Object first = rawList.get(0); // get() returns Object, not String
// String s = rawList.get(0); // COMPILE ERROR — Object cannot be assigned to String
// ... but this only fails at compile time if you try to use the typed return.
// If you cast: String s = (String) rawList.get(0); — compiles, explodes at runtime.
// ── Non-generic members are NOT affected ─────────────────────────────
// size(), isEmpty(), clear() don't involve T — accessible safely through raw type:
List raw = new ArrayList(List.of("a", "b", "c"));
int size = raw.size(); // OK — not generic, no warning
boolean mt = raw.isEmpty(); // OK — not generic, no warning
raw.clear(); // OK — not generic, no warning
// ── Two warning categories: raw type vs unchecked ─────────────────────
// Category 1 — "raw type" warning: using raw type in declaration
List rawDecl = new ArrayList(); // WARNING: List is a raw type
// Category 2 — "unchecked" warning: raw meets parameterized
List raw2 = new ArrayList();
List<String> typed;
typed = raw2; // WARNING: "unchecked assignment" — raw List assigned to List<String>
// Adding via the raw reference to a typed list:
List<String> safe = new ArrayList<>();
List dangerous = safe; // raw alias — WARNING: unchecked
dangerous.add(Integer.valueOf(99)); // compiler says nothing here
String s = safe.get(0); // ClassCastException at runtime — heap polluted
// ── Raw Iterator — generics disabled on returned generic types too ────
List<String> strings = new ArrayList<>(List.of("a", "b", "c"));
Iterator rawIter = strings.iterator(); // raw Iterator, even though strings is typed
Object element = rawIter.next(); // returns Object, not String
// String str = rawIter.next(); // COMPILE ERROR — raw Iterator.next() returns ObjectRaw Types vs Wildcard Types, and Migration Compatibility
// ── Raw type vs wildcard — what each allows ─────────────────────────
List<String> strings = new ArrayList<>(List.of("a", "b", "c"));
List<?> wildcard = strings; // unbounded wildcard
List raw = strings; // raw type
// Writing: wildcard REJECTS adds (except null); raw ACCEPTS anything
// wildcard.add("x"); // COMPILE ERROR — cannot add to List<?>
// wildcard.add(null); // null is the only add that's legal
raw.add("x"); // compiles — heap pollution added
raw.add(42); // compiles — now List<String> has an Integer in it!
// Reading: both return Object
Object wo = wildcard.get(0); // Object — element type is unknown
Object ro = raw.get(0); // Object — type parameter erased
// ── Class raw type vs Class<?> vs Class<T> ───────────────────────────
// Old API (returns raw Class — deprecated pattern):
// Class c = obj.getClass(); // raw — type checking disabled
// Modern form — unknown but type-safe:
Class<?> c = obj.getClass(); // Class<? extends Object> — wildcard
// Typed form — when T is known:
Class<String> stringClass = String.class; // Class<String> — fully typed
// The difference in usage:
Class<?> unknown = String.class;
// String s = unknown.newInstance(); // returns Object, needs cast
// Class<String> typed = unknown; // COMPILE ERROR — Class<?> not assignable to Class<String>
Class<String> typed = String.class;
String s = typed.getDeclaredConstructor().newInstance(); // returns String — no cast
// ── Migration compatibility — raw accepting parameterized ─────────────
// Legacy method written before generics:
static void legacySort(List list, Comparator comp) {
Collections.sort(list, comp); // raw types — no generic checking
}
List<Integer> ints = new ArrayList<>(List.of(3, 1, 4, 1, 5));
// Calling legacy method with parameterized types — unchecked but allowed:
legacySort(ints, Comparator.naturalOrder()); // WARNING: unchecked call — compiles and works
System.out.println(ints); // [1, 1, 3, 4, 5]
// Calling new generic method with raw type — also allowed but warned:
static <T> void modernSort(List<T> list, Comparator<? super T> comp) {
list.sort(comp);
}
List rawInts = new ArrayList(List.of(3, 1, 4));
// modernSort(rawInts, Comparator.naturalOrder()); // WARNING: unchecked — allowed for migration
// ── The correct substitutes for raw types in modern code ──────────────
// NEVER write: ALWAYS write:
// List List<?> or List<MyType>
// Map Map<?, ?> or Map<K, V>
// Class Class<?> or Class<T>
// Comparable Comparable<?> or Comparable<MyType>
// Iterator Iterator<?> or Iterator<MyType>
// When you truly need to read from an unknown-type collection:
public static void printAll(List<?> list) { // wildcard, not raw
for (Object item : list) {
System.out.println(item);
}
}
printAll(List.of("a", "b", "c")); // works
printAll(List.of(1, 2, 3)); // works
// No unchecked warnings — List<?> is the correct formUnchecked Warnings In Depth, @SuppressWarnings, and Raw Types in Reflection
// ── Four unchecked warning categories ───────────────────────────────
// 1. Unchecked assignment — raw to parameterized
List rawSource = new ArrayList(List.of("a", "b"));
List<String> typed = rawSource; // WARNING: unchecked assignment
// No ClassCastException yet — just potential heap pollution
// 2. Unchecked call — calling generic method with raw type argument
public static <T> void reverse(List<T> list) { Collections.reverse(list); }
List rawArg = new ArrayList(List.of(1, 2, 3));
reverse(rawArg); // WARNING: unchecked method invocation — T inferred as Object
// 3. Unchecked cast — explicit cast to parameterized type
Object obj = new ArrayList<>(List.of("x", "y"));
List<String> fromObj = (List<String>) obj; // WARNING: unchecked cast
// Compiles and works here because the actual object IS an ArrayList of Strings
Object obj2 = new ArrayList<>(List.of(1, 2, 3));
List<String> badCast = (List<String>) obj2; // WARNING: unchecked cast — NO exception here
String s = badCast.get(0); // ClassCastException HERE — at read time
// 4. Unchecked conversion — method return of raw type used as parameterized
@SuppressWarnings("rawtypes")
static List getRaw() { return new ArrayList(List.of("p", "q")); }
List<String> fromMethod = getRaw(); // WARNING: unchecked conversion
// ── -Xlint:unchecked — enabling full warnings ────────────────────────
// Compile with: javac -Xlint:unchecked MyClass.java
// Output shows each unchecked location and what type was involved:
// MyClass.java:12: warning: [unchecked] unchecked assignment
// List<String> typed = rawSource;
// ^
// required: List<String>, found: List
// ── @SuppressWarnings("unchecked") — with documented justification ────
// BAD — no justification:
@SuppressWarnings("unchecked")
public <T> T readFromCache(String key) {
return (T) cache.get(key); // dangerous: caller-chosen T may not match stored value
}
// GOOD — with verified invariant documented:
/**
* Reads from the cache. Safe because items are always stored via
* storeInCache(String, T) which maintains the key→T invariant.
* No other code path writes to the backing map.
*/
@SuppressWarnings("unchecked")
public <T> T readFromCache(String key) {
return (T) cache.get(key);
}
// Narrowest scope — suppress only the single statement where needed:
public Map<String, Integer> parseConfig(String json) {
Object parsed = jsonParser.parse(json);
@SuppressWarnings("unchecked")
Map<String, Integer> result = (Map<String, Integer>) parsed;
// Safe: the JSON schema guarantees string keys and integer values
return result;
}
// ── Raw types in reflection — unavoidable and idiomatic ──────────────
import java.lang.reflect.*;
// getDeclaredMethods() returns Method[] — no generic info on the array:
Method[] methods = String.class.getDeclaredMethods(); // raw, no alternative
// getReturnType() returns raw Class:
Class returnType = methods[0].getReturnType(); // raw — getReturnType() cannot be generic
// getGenericReturnType() returns Type (may be ParameterizedType, TypeVariable, etc.):
Type genericReturn = methods[0].getGenericReturnType(); // preserves generic signature
// Pattern: use raw Class when only the erasure matters, Type when you need generics:
for (Method m : String.class.getDeclaredMethods()) {
System.out.printf("%-20s erased=%-15s generic=%s%n",
m.getName(),
m.getReturnType().getSimpleName(),
m.getGenericReturnType().getTypeName());
}
// Instantiating via reflection — always returns raw type, must suppress:
@SuppressWarnings("unchecked")
public static <T> T newInstance(Class<T> clazz) throws ReflectiveOperationException {
// getDeclaredConstructor().newInstance() is safe here because clazz IS Class<T>:
return (T) clazz.getDeclaredConstructor().newInstance();
// Actually: clazz.getDeclaredConstructor().newInstance() already returns T via Class<T>.
// For Constructor<T>, newInstance() returns T — no cast needed:
}
// The truly safe form using Class<T> — no suppression needed:
public static <T> T safeNewInstance(Class<T> clazz) throws ReflectiveOperationException {
return clazz.getDeclaredConstructor().newInstance(); // returns T directly — no cast
}