What is a ClassCastException, when does it occur, and how do you prevent it?
answer
- Unchecked RuntimeException thrown by the JVM at a bad downcast
- Runtime type != cast target -> boom
- instanceof guard is null-safe; null instanceof X is false
- Casting null never throws
- Pattern matching / generics / polymorphism remove the need to cast
basics
~10 sIt is a runtime error thrown when you cast an object reference to a type the object actually isn't. Prevent it by checking the type first with instanceof before casting.
solid answer
~50 sClassCastException is an unchecked runtime exception thrown by the JVM when a narrowing reference conversion (downcast) is attempted and the object's real runtime type is not the target type or a subtype of it. The compiler can't prevent it because it only sees the static (declared) type of the reference; the JVM verifies the actual type when the cast executes. The classic fix is to guard with instanceof, which is null-safe and false for nulls. Modern Java (16+) offers instanceof pattern matching, which tests and binds the variable in one step: if (a instanceof Dog d) { d.bark(); }. Better still, prefer designs that avoid downcasts entirely: generics for collections, polymorphic methods, or sealed types with switch pattern matching. Note casting null to any reference type never throws; it just yields null. Excessive downcasting is usually a hint that polymorphism was bypassed.
go deeper
Knows it's a runtime error from a bad cast and that instanceof can guard against it.
Explains static-vs-runtime type, the null exception, and uses instanceof or pattern matching to prevent it.
Adds generics/polymorphism/sealed types as structural fixes and explains why the compiler can't prevent the cast.
Discusses erasure-driven heap pollution, where the exception actually surfaces, -Xlint:unchecked, and designing hierarchies (sealed + exhaustive switch) so unsafe casts are impossible.
## The exception itself `ClassCastException` is a subclass of `RuntimeException` (so **unchecked** — you are not forced to declare or catch it). It lives in `java.lang`. It is thrown by the **JVM**, not by your code, at the moment a **downcast** (narrowing reference conversion) executes and turns out to be a lie. ## Why it happens: static type vs runtime type A reference variable has a **static type** (what's declared in source) and points to an object with a **runtime type** (its actual class, fixed at `new`). When you write `(Dog) a`, you are *asserting* that the object `a` points to is a `Dog`. The compiler accepts the assertion as long as it's *plausible* (the types are on the same inheritance line). But it can't verify it, so the JVM inserts a check that runs when the cast executes: ``` Animal a = new Cat(); Dog d = (Dog) a; // compiles; at runtime JVM sees Cat, throws ClassCastException ``` The message typically reads like `class Cat cannot be cast to class Dog`. ## The null special case Casting `null` to any reference type **never** throws: ``` Animal a = null; Dog d = (Dog) a; // fine; d is null ``` This is because there is no object to check. `instanceof` mirrors this: `null instanceof Anything` is always `false`. ## Prevention strategies (from tactical to architectural) 1. **Guard with `instanceof`** before casting. It is null-safe: ``` if (a instanceof Dog) { Dog d = (Dog) a; d.bark(); } ``` 2. **`instanceof` pattern matching (Java 16+)** — test and bind in one expression, no redundant cast: ``` if (a instanceof Dog d) { d.bark(); } ``` 3. **Generics** — parameterized collections (`List<Dog>`) remove the casts you used to write when pulling from raw `List`/`Object`. 4. **Polymorphism** — put the behavior on the type itself (`a.makeSound()`) so you never need to know the concrete class. 5. **Sealed types + switch pattern matching (Java 17+/21)** — when you genuinely must branch on subtype, a `switch` over a sealed hierarchy is exhaustive and checked by the compiler, eliminating the unguarded cast. ## A subtle generics gotcha: heap pollution Because of **type erasure**, generic type arguments aren't checked at runtime. Unsafe raw-type usage can let the wrong type into a `List<String>`, and the ClassCastException then surfaces far away — at the *implicit* cast the compiler inserted when you read the element, not at the line that did the wrong insert. This is why `-Xlint:unchecked` warnings matter. ## Bottom line ClassCastException is the runtime cost of asking the JVM to trust a downcast. Check first (`instanceof`/pattern matching) or, better, design so you rarely downcast at all.
- Why does the compiler allow Dog d = (Dog) someAnimal; to compile even when it will fail?Because the cast is between types on the same inheritance line, so it is plausible. The compiler only knows the static type Animal; it can't know the runtime object is a Cat, so it defers the check to the JVM.
- How does instanceof pattern matching reduce ClassCastException risk?It combines the type test and the cast/binding into one construct, so you can only use the bound variable when the test passed — there is no separate, possibly-unguarded cast to get wrong.
saying these in an interview costs you the question
- Catching ClassCastException as normal control flow instead of checking type first
- Thinking the compiler catches all bad casts
- Believing casting null throws (it returns null)
- Forgetting instanceof returns false for null and assuming you still need a separate null check after it