The instanceof operator in Java
How instanceof behaves with null and impossible types, how the JVM answers a type check through the Klass super-type display and secondary supers, pattern matching with flow scoping, instanceof versus getClass() in equals, and when type checks signal a design smell.
On this page
🔍 What is the instanceof operator?
instanceof asks the JVM a yes-or-no question about an object: is this object an instance of that type, or of one of its subtypes? payment instanceof CardPayment is true for a CardPayment and for any subclass of it, false for a UpiPayment, and false for null. Since Java 16 it can also bind a typed variable in the same step: if (payment instanceof CardPayment card).
instanceof looks like the easiest operator in Java, and that's why interviewers like it. A two-line question about null or about a cast between sibling classes tells me whether you separate what the compiler decides from what the JVM decides. The follow-ups go deeper fast: the bytecode, pattern scoping, equals, and why long instanceof chains make reviewers frown.
What you’ll learn
- Predict every result of
x instanceof T:nullgivesfalse, impossible types are a compile error, everything else is decided at runtime - Explain what the
instanceofandcheckcastbytecodes do and how HotSpot answers them with the primary supers display and the secondary supers array - Write Java 16+ pattern matching with
&&, negation and early return, and know exactly where the binding variable is in scope - Choose between
instanceofandgetClass()inequals, and explain the symmetry and proxy trade-offs - Recognise when an
instanceofchain should become a polymorphic method or a sealed type with an exhaustive switch
For this lesson, we'll focus on the exact semantics (including null and compile-time errors), how HotSpot answers a type check in a few instructions, pattern matching for instanceof with its flow scoping rules, instanceof versus getClass() in equals, and when type checks are a design smell that sealed types and switch fix better.
Below the line (out of scope)
- Upcasting, downcasting and the
checkcastdecision tree: covered in Object type casting - Patterns inside
switch, guards and exhaustiveness in depth: covered in Pattern matching for switch - The full
equalsandhashCodecontract: covered in The equals() and hashCode() contracts - Sealed hierarchies themselves: covered in Sealed classes and interfaces
The 30-second answer
x instanceof T is true when x is non-null and its runtime class is T or a subtype of T, and false otherwise, so null instanceof Anything is false and never throws. The compiler rejects the check outright when the static type of x can never hold a T, such as sibling classes or a final class and an interface it doesn't implement. At runtime it compiles to the instanceof bytecode, which HotSpot answers with one load and compare for classes and a small cache plus array scan for interfaces. Since Java 16, x instanceof T t also binds t, and flow scoping makes t usable wherever the check is known to be true. In equals I use instanceof on a final class, and when I see an instanceof chain over my own types, I replace it with a polymorphic method or a sealed type with an exhaustive switch.
Interview radar
Asked very often: What does null instanceof X return, when does instanceof fail to compile, and how does if (o instanceof Card c) change the old check-then-cast code?
Senior rounds probe: How does the JVM check an interface type quickly, where exactly is a pattern variable in scope, and should equals use instanceof or getClass()?
Production angle: Type checks show up in incidents as proxy objects failing getClass() equality, NoClassDefFoundError from a check against an optional dependency, and hot interface checks that stopped scaling with core count.
01Picture it first: a venue bouncer checking wristbands
Picture a bouncer at a festival with three areas: general, VIP and backstage. A backstage wristband also gets you into VIP and general, because backstage is a "subtype" of VIP. The bouncer's question is never "is your wristband exactly VIP?" but "does your wristband grant at least VIP?". Nobody standing there (a null reference) is not let in, and there's no fight about it. And if you ask "is this parked car a VIP guest?", the organiser stops you before the gate even opens, because a car can never be a guest. That's the compile-time error.
The figure below traces the same three decisions for x instanceof T, with the compile-time gate on the left and the runtime checks on the right.
Flow of x instanceof T: javac first asks whether the static type can ever hold a T and reports a compile error if not; at runtime a null reference gives false, an object whose class is T or a subtype gives true, and anything else gives false
Only one of the four outcomes is an error, and it happens before your code runs. At runtime instanceof answers true or false and never throws a ClassCastException or NullPointerException, which is why it's the guard people put in front of a cast.
02How instanceof works under the hood
Try it yourself first
Before reading on, predict the four printed values. CardPayment and UpiPayment both extend Payment, and Refundable is an interface that only CardPayment implements.
interface Refundable { }
class Payment { }
class CardPayment extends Payment implements Refundable { }
class UpiPayment extends Payment { }
Payment card = new CardPayment();
Payment upi = new UpiPayment();
Payment missing = null;
System.out.println(card instanceof Payment); // prints: true
System.out.println(card instanceof Refundable); // prints: true
System.out.println(upi instanceof CardPayment); // prints: false
System.out.println(missing instanceof Payment); // prints: false
Notice three things:
- Subtypes count.
card instanceof Paymentistrueeven though the object's class isCardPayment. The question is "can this object be used as aPayment?", not "is its class exactlyPayment?". - The static type doesn't decide the answer. All three variables are declared as
Payment; the result depends on the object each one points to. The declared type only decides whether the line compiles. nullis never an instance of anything, not evenObject. That's whyif (x instanceof Order)doubles as a null check, and why you don't needx != null && x instanceof Order.
When instanceof doesn't compile
The compiler applies the same rule as for a cast (JLS §15.20.2): if the type of the left operand can't be cast to T, there is no object that could ever make the check true, so javac reports an error instead of letting you write dead code.
CardPayment cp = new CardPayment();
boolean a = cp instanceof UpiPayment; // ❌ incompatible types: CardPayment cannot be converted to UpiPayment
String status = "PAID";
boolean b = status instanceof Integer; // ❌ incompatible types: String cannot be converted to Integer
Object o = status;
boolean c = o instanceof Integer; // ✅ compiles, false at runtime
Payment p = cp;
boolean d = p instanceof Refundable; // ✅ compiles: some Payment subclass might implement it
int[] ids = {1, 2};
boolean e = ids instanceof Object[]; // ❌ incompatible types: int[] cannot be converted to Object[]
Object raw = List.of("a");
boolean f = raw instanceof List<String>; // ❌ Object cannot be safely cast to List<String>
boolean g = raw instanceof List<?>; // ✅ compiles
The last two lines are about erasure. At runtime the JVM only knows "this is a List", not what it holds, so a check against List<String> from an Object would be a lie. Since Java 16 the compiler allows a generic target only when the cast is provably safe, for example List<String> items checked against ArrayList<String>. Primitives aren't allowed on the left either: count instanceof Integer with an int is "unexpected type" in Java 21 (primitive patterns are a preview feature in later releases).
The bytecode: one instruction, then a cast
instanceof compiles to a JVM instruction of the same name. It pops a reference and pushes an int: 1 for a match, 0 otherwise. When you use a pattern, javac follows it with an ordinary checkcast and stores the result in the binding variable. Here's javap -c for if (p instanceof CardPayment card && card.isInternational()):
0: aload_0
1: instanceof #13 // class CardPayment
4: ifeq 29 // 0 means no match: skip the block
7: aload_0
8: checkcast #13 // class CardPayment (cannot fail here)
11: astore_1 // card
12: aload_1
13: invokevirtual #15 // Method CardPayment.isInternational:()Z
Two details are worth saying out loud. First, the JVM resolves the class named in the instruction only when the reference is non-null. If that class is missing at runtime, the first non-null check throws NoClassDefFoundError rather than returning false. Second, when the left side is an expression such as orders.get(i), javac stores its value in a hidden local first, so the expression is evaluated once even though the bytecode reads it twice. The checkcast after a successful instanceof is almost always removed by the JIT, because the type is already proven.
How HotSpot answers "is this object a T?"
Walking the hierarchy on every check would be a loop of pointer chasing, so each class's metadata (the Klass in HotSpot) carries precomputed answers. The figure shows the layout for CardPayment.
HotSpot Klass metadata for CardPayment: a primary supers display holding Object, Payment and CardPayment at fixed depths, a secondary supers array holding its interfaces, and a one-slot secondary super cache, followed by the three steps of a subtype check
These pieces work together:
- Primary supers display. A small array (8 entries) holding the class's superclass chain at fixed positions:
Objectat depth 0,Paymentat depth 1,CardPaymentat depth 2. Every class at depth 1 stores its own pointer in slot 1, so checkingx instanceof Paymentis "load slot 1 of x's class, compare withPayment". One load, one compare, no loop. - Secondary supers. Interfaces don't fit a single chain (a class can implement many, at any depth), so they live in a separate array, along with superclasses deeper than 8 levels. Arrays of objects use it too: every array type lists
CloneableandSerializable. - Secondary super cache. One slot per class remembering the last interface that matched through the slow path. A check against an interface compares the cache first, then falls back to a linear scan of the array, and on a hit writes the cache.
Class checks are nearly free, and interface checks are cheap when one interface dominates. The weakness is the write, which we'll come back to.
Pattern matching and flow scoping
Before Java 16, every type check came with a repeated type name and a cast that could drift out of sync with the check during a refactor. JEP 394 made the check, the cast and the variable one construct.
Try it yourself first
Which of the three uses of card below compile? Decide before looking at the comments.
Premium lesson
Keep reading with the Java Pass
The rest of this lesson is part of Java One-Stop Interview Prep: in-depth explanations, diagrams, model answers and how to say them in the interview.
- Real world: equal entities that weren't equal
- Advanced: scaling, design smells and the edge cases
- How to say it in the interview
From ₹199 · one-time payment · No auto-renewal
Further reading (9)
- JLS §15.20.2 The instanceof Operator
- JLS §6.3.1 Scope for Pattern Variables Introduced by Expressions
- JVMS §6.5 instanceof
- JEP 394: Pattern Matching for instanceof
- JEP 440: Record Patterns
- JEP 441: Pattern Matching for switch
- JDK-8180450: secondary_super_cache does not scale well
- Effective Java, 3rd ed., Item 10: Obey the general contract when overriding equals
- Baeldung: Java instanceof Operator