Command Palette

Search for a command to run...

PHASE 5Intermediate ~29 min· topic 5 of 13

Topic 5.5

Upcasting, Downcasting and instanceof

In one line

Upcasting (child to parent type) is automatic and always safe. Downcasting (parent to child type) needs an explicit cast, is checked at run time, and throws ClassCastException if the object isn't really that type, so you test first with instanceof.

Think of it like this

A parcel labelled 'Electronics'. Any phone can be put in a box labelled Electronics: that's always fine (upcasting). But if you open an 'Electronics' box and announce 'this is a phone!', you'd better check first, because it might be a toaster. Shouting 'phone' at a toaster doesn't make it one; the warehouse alarm goes off (ClassCastException). instanceof is opening the lid to look before you announce.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Cast
Writing (Type) expression to view a value as another type. For references, it changes only the view, not the object.
Upcasting
Treating an object as one of its parent types, like Animal a = dog;. Automatic and always safe.
Downcasting
Treating a parent-typed reference as a more specific child type, like (Dog) animal. Needs the cast syntax and is checked at run time.
ClassCastException
The exception the JVM throws when a downcast is wrong: the object isn't an instance of the target type.
instanceof
An operator that answers 'is this object a T (or a subtype of T)?' with true or false. Always false for null.
Pattern variable
The new variable in x instanceof Dog d. It is declared and assigned only when the test succeeds.
checkcast
The bytecode instruction a downcast compiles to. It verifies the object's type at run time.

Step by step

01Up is free

Assigning a Dog to an Animal variable compiles to nothing at all: no instruction, no check. The reference value (the address of the object) is copied as-is. The compiler already knows it's safe from the class hierarchy.

This happens all the time without you noticing: passing a Dog to a method that takes an Animal, adding a Dog to a List<Animal>, returning a Dog from a method declared to return Animal.

02Down needs proof

Going back down is different. An Animal variable might hold a Dog, a Cat, or a plain Animal. When you write (Dog) a, javac emits checkcast Dog. The JVM looks at the object's class pointer and checks whether that class is Dog or a subclass of Dog.

Yes: the same reference continues, now typed as Dog. No: ClassCastException, with a message naming both classes.

Down needs proofdiagram
Rendering diagram…

03What the exception says

The message names the object's real class, the type you asked for, and where each class came from (module and class loader). That last part matters in big applications where two different class loaders can each load a class called Dog; they are then different types, and a cast between them fails even though the names match.

terminal
$ java Main.java
── expected output ──
Exception in thread "main" java.lang.ClassCastException: class Cat cannot be cast to class Dog (Cat and Dog are in unnamed module of loader 'app')
at Main.main(Main.java:4)

04Test first with instanceof

a instanceof Dog asks the same question as the cast but answers with a boolean instead of an exception. The classic, pre-Java-16 shape is: test, then cast, then use.

null instanceof Anything is false, so the test also protects you from NullPointerException on the next line.

Main.javawhole filejava
if (a instanceof Dog) {
    Dog d = (Dog) a;     // safe: we just checked
    d.fetch();
}

05Pattern matching for instanceof (Java 16)

The test-then-cast shape repeats the type twice, and nothing stops you from testing Dog and casting to Cat by mistake. Java 16 made the pattern form standard: if (a instanceof Dog d). When the test passes, d is declared, already cast.

The compiler tracks where the match is definitely true. With && you can use d on the right side: a instanceof Dog d && d.age() > 3. With || you can't (the match might not have happened). After if (!(a instanceof Dog d)) return; you can use d for the rest of the method.

Main.javawhole filejava
if (a instanceof Dog d && d.isPuppy()) {
    d.fetch();
}

if (!(obj instanceof String s)) {
    return;
}
System.out.println(s.length());   // s is in scope here

06Casts the compiler refuses

The compiler rejects a cast that can never succeed. A Dog variable can never hold a Cat, since they're siblings, so (Cat) dog is a compile error: incompatible types: Dog cannot be converted to Cat. Same for (Integer) someString.

Interfaces are subtler. (Runnable) someDog compiles if Dog is not final, because some subclass of Dog might implement Runnable. If Dog is final, no such subclass can exist, and the compiler rejects it. This is one reason final and sealed (Topics 5.10, 5.11) help the compiler help you.

terminal
$ javac Main.java
── expected output ──
Main.java:2: error: incompatible types: Dog cannot be converted to Cat
public class Main { public static void main(String[] a) { Dog d = new Dog(); Cat c = (Cat) d; } }
^
1 error

Try it yourself

  1. 1

    Rewrite play() with patterns

    In the first example, rewrite play() using if (a instanceof Dog d) d.fetch(); else if (a instanceof Cat c) c.purr();. The output must not change.

  2. 2

    Cause and read a ClassCastException

    In the second example, remove the try/catch around (Integer) o and run. Read the full message: it names java.lang.String, java.lang.Integer, and the module java.base both come from.

  3. 3

    Find the compile-time refusals

    Add Cat c = (Cat) new Dog(); to main and compile. Then try Runnable r = (Runnable) new Dog();: it compiles (Dog isn't final). Mark Dog as final and compile again to see the error appear.

Code & diagrams

Up, down, and instanceof New tab
Sign in to run this example in your browser.

Expected output

Playing with: dog
  fetches the ball
Playing with: cat
  purrs
Playing with: animal
same object: true
null instanceof Animal: false
cast of null: null
A wrong downcast throws ClassCastException New tab

A Puppy passes a cast to Dog: a cast succeeds for the target type or any subclass of it.

Sign in to run this example in your browser.

Expected output

Dog   -> Dog: OK, it is a Dog
Puppy -> Dog: OK, it is a Puppy
Cat   -> Dog: Failed: ClassCastException
String is not an Integer
Pattern matching for instanceof Java 16+ New tab
Sign in to run this example in your browser.

Expected output

big int 42
small int 7
string of length 2
something else: []
double 2.5
something else: [null]

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Cast between sibling classes

Cast a Dog variable directly to Cat.

Main.javawhole filejava
class Animal { } class Dog extends Animal { } class Cat extends Animal { }
public class Main { public static void main(String[] a) { Dog d = new Dog(); Cat c = (Cat) d; } }
terminal
$ javac Main.java
── what you'll see ──
Main.java:2: error: incompatible types: Dog cannot be converted to Cat
public class Main { public static void main(String[] a) { Dog d = new Dog(); Cat c = (Cat) d; } }
^
1 error

Break #2

Downcast without checking

Store a Cat in an Animal variable and cast it to Dog with no instanceof test.

Main.javawhole filejava
class Animal { }
class Dog extends Animal { }
class Cat extends Animal { }
public class Main { public static void main(String[] a) { Animal x = new Cat(); Dog d = (Dog) x; } }
terminal
$ java Main.java
── what you'll see ──
Exception in thread "main" java.lang.ClassCastException: class Cat cannot be cast to class Dog (Cat and Dog are in unnamed module of loader 'app')
at Main.main(Main.java:4)

Myth vs fact

Myth

Casting converts an object into another type.

Fact

Reference casts never change the object. They change only the type the compiler uses for that expression, plus a run-time check for downcasts.

Myth

You should always check for null before instanceof.

Fact

instanceof already returns false for null. if (o != null && o instanceof Dog) has a redundant check.

Myth

Upcasting loses the subclass's data.

Fact

Nothing is lost. The object is unchanged; you just can't call Dog-only methods through an Animal-typed reference. Overridden methods still run Dog's code.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    checkcast and instanceof are fast in HotSpot: each class stores a short 'primary supers' display of its superclass chain (up to depth 8) for a constant-time check, plus a one-element cache for secondary supers (interfaces and deep chains). A cache that keeps flipping between two interfaces at a hot site was a real JDK performance issue (JDK-8180450), addressed by a redesigned secondary-supers lookup in JDK 23.

  • ▸

    Generic code is full of hidden casts: String s = list.get(0); compiles to invokeinterface List.get followed by checkcast String, because generics are erased (Topic 8.6). That's where a 'heap pollution' ClassCastException appears, far from the line that put the wrong object in.

  • ▸

    In equals, o instanceof Point lets subclasses be equal to their parent, while getClass() == o.getClass() requires the exact same class. instanceof breaks symmetry if a subclass adds fields to equality; getClass() breaks Liskov substitution. Records and final classes avoid the dilemma, which is part of why value classes are made final.

  • ▸

    Since Java 21, instanceof also accepts record patterns, e.g. o instanceof Point(int x, int y) (Topic 11.3), and on Java 21 an instanceof whose pattern type is the same as the expression's type is allowed (it was an error in Java 16 and 17).

Remember this

  1. 1

    Casting a reference never changes the object. It only changes the *declared type* through which you look at it. (Dog) animal doesn't convert anything; it tells the compiler 'trust me, treat this as a Dog' and asks the JVM to verify that claim. (Casting *primitives*, like (int) 3.9, is different: it really converts the value; see Topic 1.6.)

  2. 2

    Upcasting goes from a subtype to a supertype: Animal a = new Dog();. It's implicit (no cast syntax needed) and can never fail, because every Dog is an Animal. You lose access to Dog-only methods through that reference, but the object keeps them.

  3. 3

    Downcasting goes from a supertype to a subtype: Dog d = (Dog) a;. It needs explicit cast syntax and compiles to a checkcast bytecode instruction. At run time, if the object isn't a Dog (or a subclass of Dog), the JVM throws ClassCastException. A null reference passes any cast and stays null.

  4. 4

    x instanceof T returns true if x is not null and its object could be cast to T without an exception. It is false for null, so it doubles as a null check. The compiler rejects instanceof tests and casts that could never succeed, such as String to Integer, or between two sibling classes.

  5. 5

    Pattern matching for instanceof (Java 16) combines the test and the cast: if (a instanceof Dog d) { d.fetch(); }. The variable d exists only where the test is known to be true, and it works with && and with negation (if (!(o instanceof String s)) return; s.length();). It removes the repeated type name and the risk of casting to the wrong type.

  6. 6

    Frequent downcasting is a design smell. If code keeps asking 'are you a Dog? are you a Cat?', the behaviour probably belongs in an overridden method (Topic 5.4), or the type should be sealed and handled with an exhaustive switch (Topic 5.11).

Explain it without notes

01

What's the difference between upcasting and downcasting? Which one can fail, and when?

02

What exactly does (Dog) a do at run time?

03

When does the compiler reject a cast outright?

04

What does pattern matching for instanceof add over test-then-cast?

Practice

01

Write static double totalArea(Object[] items) that sums the area of every Circle and Square in the array (each with its own fields) and ignores anything else, using pattern matching.

02

Given Animal[] zoo = { new Dog(), new Cat(), new Dog() }, count how many are Dogs using instanceof.

03

Write a method that takes an Object and returns its length if it's a String, its size if it's a java.util.List, and -1 otherwise.

Trade-offs

  • ↔

    instanceof chains are quick to write but every new subtype means finding and updating every chain. Overridden methods (or sealed types with exhaustive switches) let the compiler find the places for you.

  • ↔

    Downcasting is sometimes unavoidable at boundaries (deserialisation, equals, legacy APIs returning Object). Keep the casts at the edge and work with precise types inside.

Done when you can

  • Done when you can explain why upcasting is free and downcasting is checked.

  • Done when you can predict whether a cast fails to compile, fails at run time, or succeeds.

  • Done when you can use instanceof patterns with && and negation.

  • Done when you know null instanceof X is false and (X) null is allowed.

  • Done when you can recognise downcast-heavy code as a design smell and suggest overriding instead.