JAVA / INHERITANCE AND POLYMORPHISM
instanceof checks before a risky cast
Guard downcasts with instanceof, bind the value with a pattern variable, and order type checks so a specific subtype is never swallowed by its parent.
What you will learn
- Guard a downcast with instanceof so a wrong type takes a branch instead of crashing
- Test, cast and name in one step with `if (shape instanceof Circle c)`
- Lean on `null instanceof T` being false instead of adding a separate null check
- Order instanceof chains most specific first; the test is true for every supertype
Understanding instanceof checks before a risky cast
A cast like `(Circle) shape` is a command: it tells the JVM to treat the object as a Circle and to throw ClassCastException if it is not one. instanceof is the matching question: it inspects the object the reference actually points to and returns true when that object's class is the named type or any subtype of it. Asking first and casting second means the risky line only runs on the path where the answer was yes. The declared type of the reference matters only at compile time, where it decides whether the question is legal to ask; the answer comes from the object itself at runtime.
Two details make the guard airtight. First, `null instanceof Circle` is false, because there is no object to inspect, so one instanceof covers both wrong type and nothing here. Second, since Java 16 you can write `if (shape instanceof Circle c)`, which runs the test and, on success, binds the already-cast value to c. The pattern variable is in scope exactly where the compiler can prove the test succeeded, which is why the right side of `&&` can use it, why `||` does not compile, and why `if (!(o instanceof String s)) return;` leaves s usable for the rest of the method.
Because instanceof is true for a type and everything below it, a chain of checks has to run most specific first: if Puppy extends Dog and you test Dog first, the Puppy branch is dead in practice and no compiler warning points that out. That ordering rule is also a design hint. A long chain over classes you wrote usually means the behaviour belongs in an overridden method on the supertype, while the check earns its place when you cannot add a method: an Object parameter in equals, values coming out of a parser, types from a library you do not own.
import java.util.ArrayList;
import java.util.List;
class Shape { }
class Circle extends Shape {
private final double radius;
Circle(double radius) { this.radius = radius; }
double radius() { return radius; }
}
class Square extends Shape {
private final double side;
Square(double side) { this.side = side; }
double side() { return side; }
}
class Triangle extends Shape { }
public class Main {
static String describe(Shape s) {
if (s instanceof Circle) {
Circle c = (Circle) s; // safe: the test above already answered yes
return "circle, radius " + c.radius();
}
if (s instanceof Square sq) { // test and bind in one step
return "square, side " + sq.side();
}
return "no rule for " + (s == null ? "null" : s.getClass().getSimpleName());
}
public static void main(String[] args) {
List<Shape> shapes = new ArrayList<>();
shapes.add(new Circle(2));
shapes.add(new Square(3));
shapes.add(new Triangle());
shapes.add(null);
for (Shape s : shapes) {
System.out.println(describe(s));
}
}
}instanceof asks the object what it really is, so pairing it with the cast turns a possible ClassCastException into a branch you control.
Worked examples
null answers false, so the guard holds
Shows that instanceof never throws on a null reference and that a false answer keeps the cast unreachable.
public class Main {
public static void main(String[] args) {
Object o = null;
System.out.println(o instanceof String);
if (o instanceof String s) {
System.out.println("length " + s.length());
} else {
System.out.println("no string here, no cast attempted");
}
Object p = "hello";
System.out.println(p instanceof String);
System.out.println(p instanceof Integer);
System.out.println(((String) p).length());
}
}Example explained
Line 1`o instanceof String` prints false because o holds no object whose class could be compared.
Line 2The pattern branch is skipped for the same reason, so `s.length()` is never reached and nothing throws.
Line 3`p instanceof Integer` is false yet still compiles, because a variable declared Object could in principle hold an Integer.
Line 4The explicit cast on the last line is only safe because the true two lines earlier established the type.
Most specific check first
Demonstrates that instanceof is true for every type above the object in the chain, so a broad test hides a narrow one.
class Animal { }
class Dog extends Animal { }
class Puppy extends Dog { }
public class Main {
static String wrongOrder(Animal a) {
if (a instanceof Dog) return "dog";
if (a instanceof Puppy) return "puppy";
return "animal";
}
static String rightOrder(Animal a) {
if (a instanceof Puppy) return "puppy";
if (a instanceof Dog) return "dog";
return "animal";
}
public static void main(String[] args) {
Animal a = new Puppy();
System.out.println(wrongOrder(a));
System.out.println(rightOrder(a));
System.out.println(a instanceof Dog);
System.out.println(a instanceof Animal);
}
}Example explained
Line 1`a instanceof Dog` is true for a Puppy, so wrongOrder returns before the Puppy test is ever evaluated.
Line 2rightOrder asks the narrowest question first, so the Puppy branch wins for the same object.
Line 3The Puppy line in wrongOrder is legal Java, not unreachable code, so javac reports nothing.
Line 4The final two prints show a single object answering true to every type above it in the hierarchy.
Where the pattern variable is visible
Shows the two scopes a pattern variable gets: after a negated test with an early exit, and on the right side of &&.
public class Main {
static int lengthOrMinusOne(Object o) {
if (!(o instanceof String s)) {
return -1;
}
return s.length();
}
static boolean sameLength(Object a, Object b) {
return a instanceof String s && b instanceof String t && s.length() == t.length();
}
public static void main(String[] args) {
System.out.println(lengthOrMinusOne("stack"));
System.out.println(lengthOrMinusOne(42));
System.out.println(sameLength("abc", "xyz"));
System.out.println(sameLength("abc", 7));
}
}Example explained
Line 1`!(o instanceof String s)` makes s visible after the if, because reaching that code proves the test passed.
Line 2Passing 42 boxes it to an Integer, which fails the test, so the method leaves through the early return.
Line 3In sameLength, s and t are usable to the right of each `&&` because the operand before them must have been true.
Line 4The last call short-circuits at `b instanceof String` and never compares any lengths.
Important notes
instanceof only compiles when the cast could ever succeed: with a String variable, `s instanceof Integer` is a compile error rather than false, because the two classes are unrelated.
Generic arguments are erased at runtime, so `o instanceof List<String>` will not compile; test `o instanceof List<?>` and inspect the elements separately.
Common mistakes
Testing the parent before the child, as in `instanceof Dog` before `instanceof Puppy`, so the Puppy branch never runs and nothing in the build complains.
Assuming the plain check narrows the variable: after `if (o instanceof String)`, calling `o.length()` still fails to compile because o is declared Object, so you need a cast or a pattern variable.
Writing the cast outside the guarded block, or checking one reference and casting another, which leaves the cast unprotected and brings ClassCastException back.
Try it yourself
Change, predict, then run
Build a `List<Object>` holding "kiro", 7, 3.5 and null, then write `static int totalOfIntegers(List<Object> items)` that uses a single `instanceof Integer n` guard to add only the Integer entries. Print the total and how many entries were skipped.
Open the Java workspaceCheck your understanding
Why can `if (x instanceof Circle) { ((Circle) x).radius(); }` never throw ClassCastException, even when x is null or refers to a Square?
- instanceof returns false in both cases, so the cast inside the block is never reached
- The compiler checks the cast at compile time and proved it can only ever succeed
- Casting null throws NullPointerException instead, and a Square is silently converted
- instanceof throws NullPointerException on null, and the exception skips the block
Show answer
instanceof inspects the object the reference points to: null has no class so the test is false, and a Square is not a Circle so the test is false again, leaving the cast unreachable on both paths. Option 3 is tempting because null feels dangerous, but casting null is perfectly legal and throws nothing; the danger would be the method call afterwards, and here neither line ever runs.