Upcasting, Downcasting & instanceof
Upcasting, Downcasting & instanceof
Polymorphism lets you write code that works with a parent type while the actual object at runtime might be any subclass. To do that well, you need to understand two operations: upcasting (treating a subclass object as its parent type) and downcasting (converting it back to the specific subclass). The instanceof operator — and Java 16+'s pattern matching form of it — keeps those conversions safe.
Upcasting — the safe direction
Upcasting means assigning a subclass reference to a parent-type variable. Java does this implicitly because it is always safe: a Dog is always an Animal, so no data can be lost.
Animal a = new Dog("Rex"), the declared (compile-time) type of a is Animal, but the runtime type is still Dog. Method calls respect the runtime type (that is polymorphism from the previous lesson), but the compiler only lets you call methods that exist on Animal. So a.fetch() would be a compile error — even though the object really is a Dog.
Downcasting — the explicit direction
Downcasting means converting the parent-type reference back to a more specific subclass type. You must write the cast explicitly because the compiler cannot guarantee at compile time that the object is actually that subclass. If you get it wrong, Java throws a ClassCastException at runtime.
ClassCastException. Use instanceof to guard every downcast.
Checking types with instanceof
The instanceof operator tests whether an object is an instance of a given class (or any of its subclasses). Use it to guard a downcast:
This pattern is safe but a little verbose: you check, then cast, and end up with a second variable. Java 16 introduced a cleaner way.
Pattern matching for instanceof (Java 16+)
With pattern matching, the instanceof check and the cast happen in a single expression, and Java binds the result to a new variable automatically:
The variable d is only in scope inside the if block (and only where Java can prove the test succeeded). This eliminates an entire category of accidental ClassCastException errors.
A realistic example
Imagine a list of mixed Animal objects. You want to call type-specific behaviour on each one:
if/else instanceof chains, that is often a sign that the behaviour belongs inside an overridden method. instanceof is the right tool when you genuinely need type-specific behaviour that cannot be put into the parent class — for example, when working with third-party classes you cannot change, or when mixing unrelated hierarchies.
instanceof with inheritance depth
instanceof returns true for the object's own class and any ancestor in the hierarchy:
Summary
- Upcasting is implicit and always safe — a subclass reference can be stored in a parent-type variable.
- Downcasting requires an explicit cast and can fail at runtime if the object is the wrong type.
- Use
instanceofto guard every downcast and avoidClassCastException. - Java 16+ pattern matching for instanceof combines the test and the cast in one step, binding a typed variable automatically.
instanceofalso returnstruefor ancestor types, not just the exact class.