skip to content

What is the difference between a raw type, a parameterized type, and Box<Object>, and why does using a raw type defeat the purpose of generics?

level: middleimportance: should knowfreq 55%

answer

  1. Raw = no angle brackets (Box); checking OFF; unchecked warning
  2. Parameterized = Box<String>; checking ON
  3. Box<Object> is parameterized, NOT raw — checking stays on
  4. Raw assignable to any Box<...> (the safety hole); Box<Object> != Box<String>
  5. Want 'any type' safely? use wildcard Box<?>, not raw

basics

~20 s

A raw type is using a generic class with no type argument, like Box. A parameterized type supplies one, like Box<String>. A raw Box turns off generic type checking and forces casts, bringing back the runtime errors generics were meant to prevent, so the compiler warns against it. Box<Object> still keeps type checking on.

solid answer

~50 s

A raw type is a generic class used without any type argument: just Box. A parameterized type supplies the argument: Box<String>. Raw types exist only for backward compatibility with pre-generics (Java 4) code; using one switches off generic type checking for that reference, so the compiler stops enforcing what you put in and requires casts coming out — reintroducing the ClassCastException risk generics removed. You also get an 'unchecked' warning. Box<Object> is *not* the same as raw Box: it is a fully parameterized type where the element type is Object, so type checking stays on. The key practical difference is assignment compatibility and method signatures — passing a raw Box anywhere a Box<String> is expected compiles with a warning and can corrupt type safety, whereas Box<Object> and Box<String> are unrelated parameterized types the compiler keeps strictly separate. Rule of thumb: never use raw types in new code.

go deeper

for a junior

Recognizes that Box with no <> is a raw type and Box<String> is the proper form, and that raw types produce warnings.

for a middle

Clearly distinguishes raw vs Box<Object> vs Box<String>, explains why raw defeats safety, and knows to use Box<?> for 'any type'.

for a senior

Explains assignment compatibility (raw assignable to any Box<...>; invariance keeps Box<Object> separate from Box<String>) and that raw usage erases even unrelated generic signatures.

for a principal

Sets policy (treat unchecked as errors, ban raw types in new code, wildcard guidance) and reasons about safe interop with legacy pre-generics APIs at module boundaries.

## Three things people confuse For `class Box<T> { ... }` there are three distinct ways the name `Box` can appear, and conflating them is a classic source of bugs and warnings. ### 1. Parameterized type — `Box<String>` You supply a concrete **type argument**. The compiler enforces it everywhere: `set` only accepts `String`, `get` returns `String` with no cast. This is the normal, intended usage. ### 2. Raw type — `Box` You use the generic class with **no** angle brackets at all: ```java Box b = new Box(); // raw type b.set("hi"); b.set(42); // compiles! no type checking Object o = b.get(); // returns Object; you must cast to use it ``` A **raw type** is a generic type used without type arguments. It exists *only* for backward compatibility: code written before generics (Java 1.4 and earlier) used `Box` with no parameters, and that code still had to compile after generics were added in Java 5. Using a raw type **turns off generic type checking** for that reference. The compiler emits an **unchecked warning** because it can no longer guarantee type safety. ### 3. `Box<Object>` — a parameterized type, NOT raw This is the subtle one. `Box<Object>` is a *fully parameterized* type whose argument happens to be `Object`. Type checking is **still on**: ```java Box<Object> b = new Box<>(); b.set("hi"); // ok, String is an Object b.set(42); // ok, Integer is an Object (autoboxed) Object o = b.get(); // returns Object — but checking was never disabled ``` The difference from raw is visible in **assignment and signatures**: ```java void takesStringBox(Box<String> b) { ... } Box raw = new Box(); // raw Box<Object> objBox = new Box<>(); Box<String> strBox = new Box<>(); takesStringBox(raw); // compiles, with an UNCHECKED WARNING (unsafe) takesStringBox(objBox); // COMPILE ERROR: Box<Object> is not Box<String> takesStringBox(strBox); // ok ``` A raw `Box` is assignable (with a warning) to *any* `Box<...>`, which is exactly how it punches a hole in type safety. `Box<Object>` is a distinct parameterized type and the compiler keeps it strictly separate from `Box<String>` — generics are **invariant**, so `Box<Object>` is *not* a supertype of `Box<String>`. ## Why raw types defeat the purpose Generics exist to move type errors from **runtime** (`ClassCastException`) to **compile time**. A raw type opts out of that: ```java List raw = new ArrayList(); // raw List raw.add("hello"); raw.add(42); List<String> strings = raw; // unchecked warning String s = strings.get(1); // ClassCastException at RUNTIME — the bug generics were meant to catch ``` The whole investment in generics — compile-time safety, no casts — is thrown away the moment you use a raw reference, and the danger can propagate to other, properly-typed code through assignment. ## How the compiler treats raw types When you use a raw type, the compiler erases **all** the generics in that type's methods, not just the missing argument — so even methods that didn't mention `T` lose their generic signatures. This is why raw usage is more dangerous than it looks. ## Practical guidance - **Never** introduce raw types in new code. If you truly mean "any type," use a **wildcard** `Box<?>` (which keeps type safety: you can read as `Object` but cannot put non-null values in) rather than raw `Box`. - Reserve `Box<Object>` for when you genuinely want a box of arbitrary objects *and* you want to insert into it. - Treat unchecked warnings as errors in your build where possible. ## Why it matters Knowing the raw / `Box<?>` / `Box<Object>` distinction is the difference between safely interoperating with legacy code and silently reintroducing the runtime cast failures generics were designed to eliminate.

  • If you want a method that accepts a Box of any element type, what should the parameter be?
    Box<?> (an unbounded wildcard), not raw Box. Box<?> keeps type safety: you can read elements as Object and call type-independent methods, but the compiler forbids inserting anything except null, preventing the corruption a raw type allows. Raw Box would compile away all checks.
  • Why is Box<String> not assignable to Box<Object> even though String is an Object?
    Generics are invariant: parameterized types do not inherit along their type argument's hierarchy. If Box<String> were a Box<Object>, you could call set(42) through the Box<Object> view and corrupt the String box. Invariance prevents that; use wildcards (? extends/? super) when you need controlled variance.

saying these in an interview costs you the question

  • Treating Box<Object> as equivalent to raw Box — Box<Object> keeps type checking
  • Assuming Box<Object> is a supertype of Box<String> (generics are invariant)
  • Thinking raw types are fine 'because they compile' — they only warn, and reintroduce runtime ClassCastException
  • Using raw Box to mean 'any type' instead of the safe wildcard Box<?>

context