Java Tutorial
🔍

Java instanceof Operator

Java instanceof Operator

Every time you write a polymorphic method that accepts a parent type, there comes a moment when you need to know what the object actually is before calling a subclass-specific method. The instanceof operator answers that question. It checks whether an object is an instance of a given type at runtime and returns a boolean — true if the object is compatible with the type, false if it is not.

That sounds simple enough. What trips up beginners is everything around it: what "compatible" actually means in Java's type system, how null behaves, when using instanceof signals a design problem, and how pattern matching in Java 16 changed the way the operator is written.

What Does instanceof Do?

The instanceof operator evaluates two things at once: whether an object's actual runtime type is the specified type, or a subtype of it, or an implementation of it. The result is always a boolean.

Syntax:

  object instanceof ClassName
  object instanceof InterfaceName

Returns:
  true  — if object is an instance of ClassName or any subclass of it
  true  — if object's class implements InterfaceName
  false — if the types are incompatible
  false — if object is null (always false for null, never throws)

The compiler also enforces a compile-time sanity check: if the types being compared have no possible relationship in the class hierarchy, the compiler rejects the expression before the program ever runs.

Basic instanceof Usage

// File: InstanceofBasics.java public class InstanceofBasics { static class Animal {} static class Dog extends Animal { public void fetch() { System.out.println("Dog fetches!"); } } static class Cat extends Animal { public void purr() { System.out.println("Cat purrs..."); } } public static void main(String[] args) { Animal dog = new Dog(); Animal cat = new Cat(); Animal ref = null; // instanceof checks against the actual runtime type, not the reference type System.out.println("dog instanceof Animal : " + (dog instanceof Animal)); // true — Dog IS-A Animal System.out.println("dog instanceof Dog : " + (dog instanceof Dog)); // true — actual type System.out.println("dog instanceof Cat : " + (dog instanceof Cat)); // false — not a Cat System.out.println("cat instanceof Dog : " + (cat instanceof Dog)); // false System.out.println("cat instanceof Animal : " + (cat instanceof Animal)); // true // null always returns false — never throws NullPointerException System.out.println("null instanceof Animal: " + (ref instanceof Animal)); // false System.out.println("null instanceof Dog : " + (ref instanceof Dog)); // false } }
Output:
dog instanceof Animal : true
dog instanceof Dog    : true
dog instanceof Cat    : false
cat instanceof Dog    : false
cat instanceof Animal : true
null instanceof Animal: false
null instanceof Dog   : false

Two things stand out. First, a Dog object returns true for both Dog and Animal — the inheritance chain is traversed. Second, null instanceof AnyType always returns false — the operator is specifically designed to handle null safely without throwing.

instanceof With Interfaces

instanceof works identically with interfaces. If an object's class implements an interface — directly or through a parent class — the check returns true.

// File: InterfaceInstanceofDemo.java public class InterfaceInstanceofDemo { interface Driveable { void drive(); } interface Chargeable { void charge(); } static class Vehicle implements Driveable { @Override public void drive() { System.out.println("Vehicle drives."); } } static class ElectricCar extends Vehicle implements Chargeable { @Override public void drive() { System.out.println("Electric car drives silently."); } @Override public void charge() { System.out.println("Electric car charges."); } } static class PetrolCar extends Vehicle { @Override public void drive() { System.out.println("Petrol car drives."); } } public static void describeVehicle(Vehicle v) { System.out.println("Is Driveable : " + (v instanceof Driveable)); System.out.println("Is Chargeable : " + (v instanceof Chargeable)); System.out.println("Is ElectricCar: " + (v instanceof ElectricCar)); System.out.println("Is PetrolCar : " + (v instanceof PetrolCar)); System.out.println(); } public static void main(String[] args) { Vehicle electric = new ElectricCar(); Vehicle petrol = new PetrolCar(); System.out.println("--- ElectricCar ---"); describeVehicle(electric); System.out.println("--- PetrolCar ---"); describeVehicle(petrol); // Safe downcast pattern — check before cast if (electric instanceof Chargeable chargeable) { chargeable.charge(); } } }
Output:
--- ElectricCar ---
Is Driveable  : true
Is Chargeable : true
Is ElectricCar: true
Is PetrolCar  : false

--- PetrolCar ---
Is Driveable  : true
Is Chargeable : false
Is ElectricCar: false
Is PetrolCar  : true

Electric car charges.

ElectricCar is Driveable because it extends Vehicle which implements it, and it is Chargeable because it implements it directly. instanceof traverses both the class hierarchy and the interface implementation list.

Pattern Matching instanceof — Java 16+

Before Java 16, the safe-downcast pattern required three lines for a single operation: the instanceof check, the explicit cast, and the new variable declaration. Java 16 introduced pattern matching that collapses all three into one expression.

// File: PatternMatchingComparison.java public class PatternMatchingComparison { static class Shape {} static class Circle extends Shape { double radius; Circle(double r) { radius = r; } } static class Rectangle extends Shape { double w, h; Rectangle(double w, double h) { this.w=w; this.h=h; } } static class Triangle extends Shape { double b, h; Triangle(double b, double h) { this.b=b; this.h=h; } } // Old style — Java 15 and below public static double areaOldStyle(Shape shape) { if (shape instanceof Circle) { Circle c = (Circle) shape; // redundant cast after check return Math.PI * c.radius * c.radius; } else if (shape instanceof Rectangle) { Rectangle r = (Rectangle) shape; // redundant cast return r.w * r.h; } else if (shape instanceof Triangle) { Triangle t = (Triangle) shape; // redundant cast return 0.5 * t.b * t.h; } return 0; } // New style — Java 16+ pattern matching public static double areaPatternMatch(Shape shape) { if (shape instanceof Circle c) { // check + cast + bind in one return Math.PI * c.radius * c.radius; } else if (shape instanceof Rectangle r) { return r.w * r.h; } else if (shape instanceof Triangle t) { return 0.5 * t.b * t.h; } return 0; } public static void main(String[] args) { Shape[] shapes = { new Circle(5), new Rectangle(4, 6), new Triangle(3, 8) }; for (Shape shape : shapes) { System.out.printf("%-12s area = %.2f%n", shape.getClass().getSimpleName(), areaPatternMatch(shape)); } } }
Output:
Circle       area = 78.54
Rectangle    area = 24.00
Triangle     area = 12.00

In areaPatternMatch, if (shape instanceof Circle c) does three things at once: checks the runtime type, casts to Circle, and binds the result to the variable c — which is scoped only to that if branch. There is no separate cast line. This is the preferred style from Java 16 onwards.

instanceof vs getClass() — Comparison Table

Both instanceof and getClass() check object types, but they behave differently in inheritance hierarchies. Choosing the wrong one introduces subtle bugs.

AspectinstanceofgetClass() equality
Syntaxobj instanceof ClassNameobj.getClass() == ClassName.class
ReturnsbooleanClass object (needs == comparison)
Inheritance awareYes — returns true for subclassesNo — exact class match only
Null safeYes — returns false for nullThrows NullPointerException for null
Interface checkYesNo — interfaces have no instances
Used forSafe type check before downcastExact type identity (rare need)
Pattern matchingYes — Java 16+No
equals() best practiceAvoid (too permissive for equality)Preferred inside equals() for strict type check
Common in productionVery common — polymorphism, visitorRare — mostly inside equals()

The key practical difference: instanceof returns true for subclasses, getClass() equality does not. For most polymorphic code, instanceof is what you need. For equals() implementations where you need to ensure two objects are exactly the same class (not just related), getClass() equality is the correct choice.

// File: InstanceofVsGetClass.java public class InstanceofVsGetClass { static class Animal {} static class Dog extends Animal {} public static void main(String[] args) { Animal dog = new Dog(); // instanceof — true for the actual type AND all supertypes System.out.println("instanceof Dog : " + (dog instanceof Dog)); // true System.out.println("instanceof Animal : " + (dog instanceof Animal)); // true // getClass() equality — true only for the exact runtime type System.out.println("getClass == Dog : " + (dog.getClass() == Dog.class)); // true System.out.println("getClass == Animal: " + (dog.getClass() == Animal.class)); // false — not exactly Animal } }
Output:
instanceof Dog    : true
instanceof Animal : true
getClass == Dog   : true
getClass == Animal: false

instanceof in switch Expressions — Java 21

Java 21 extended pattern matching to switch, allowing type patterns directly as case labels. This replaces long if-else instanceof chains with a structured, exhaustive expression.

// File: SwitchPatternDemo.java public class SwitchPatternDemo { sealed interface Notification permits SmsNotification, EmailNotification, PushNotification {} record SmsNotification(String phone, String message) implements Notification {} record EmailNotification(String email, String subject) implements Notification {} record PushNotification(String deviceId, String title) implements Notification {} public static String describe(Notification notification) { // switch with type patterns — exhaustive over sealed hierarchy return switch (notification) { case SmsNotification sms -> "SMS to " + sms.phone() + ": " + sms.message(); case EmailNotification mail -> "Email to " + mail.email() + " — " + mail.subject(); case PushNotification push -> "Push to " + push.deviceId() + ": " + push.title(); }; } public static void main(String[] args) { Notification[] notifications = { new SmsNotification("9876543210", "Your OTP is 847201"), new EmailNotification("dev@company.in", "Build pipeline passed"), new PushNotification("device-abc-123", "Order delivered!") }; for (Notification n : notifications) { System.out.println(describe(n)); } } }
Output:
SMS to 9876543210: Your OTP is 847201
Email to dev@company.in — Build pipeline passed
Push to device-abc-123: Order delivered!

Because Notification is a sealed interface, the compiler knows all permitted implementations. The switch is exhaustive — no default case needed. If you add a new Notification type without adding a case, the compiler immediately reports an error. This is a significant safety improvement over if-else instanceof chains, which silently skip new types.

Basic Example

public class Main { static class Vehicle {} static class Car extends Vehicle { void openTrunk() { System.out.println("Trunk opened."); } } static class Bike extends Vehicle { void kickStart() { System.out.println("Bike kick-started."); } } public static void main(String[] args) { Vehicle[] vehicles = { new Car(), new Bike(), new Vehicle() }; for (Vehicle v : vehicles) { if (v instanceof Car car) { car.openTrunk(); } else if (v instanceof Bike bike) { bike.kickStart(); } else { System.out.println("Generic vehicle — nothing special to do."); } } } }

Output

Trunk opened.
Bike kick-started.
Generic vehicle — nothing special to do.
  1. ›Each element in the array is declared as Vehicle, but instanceof checks the object's actual runtime type — Car, Bike, or plain Vehicle.
  2. ›v instanceof Car car checks the type, casts it, and binds the result to car in one step — the pattern-matching form introduced in Java 16.
  3. ›Only when the check succeeds can openTrunk() or kickStart() be called — methods that exist only on the specific subclass, not on Vehicle.
  4. ›This avoids an unsafe cast and the ClassCastException it could throw if the object were not actually that type.

instanceof and Inheritance Depth

instanceof traverses the entire inheritance chain, not just one level. Understanding how deep it reaches prevents surprises in larger hierarchies.

// File: InheritanceDepthDemo.java public class InheritanceDepthDemo { static class A {} static class B extends A {} static class C extends B {} public static void main(String[] args) { C c = new C(); // instanceof traverses all levels upward System.out.println("c instanceof C : " + (c instanceof C)); // true — exact type System.out.println("c instanceof B : " + (c instanceof B)); // true — parent System.out.println("c instanceof A : " + (c instanceof A)); // true — grandparent Object obj = new C(); System.out.println("obj instanceof Object : " + (obj instanceof Object)); // true System.out.println("obj instanceof C : " + (obj instanceof C)); // true System.out.println("obj instanceof B : " + (obj instanceof B)); // true System.out.println("obj instanceof A : " + (obj instanceof A)); // true } }
Output:
c instanceof C : true
c instanceof B : true
c instanceof A : true
obj instanceof Object : true
obj instanceof C      : true
obj instanceof B      : true
obj instanceof A      : true

A C object passes instanceof checks for C, B, A, and Object — every type in its ancestry. This is why instanceof is called a type compatibility check rather than an exact type check. When you need exact type identity, use getClass().

Best Practices

Prefer pattern matching over the old check-then-cast pattern. Writing instanceof on one line and then casting on the next is redundant from Java 16 onwards. The pattern matching syntax if (obj instanceof Dog dog) is shorter, safer, and scopes the variable correctly. Most modern Java teams enforce this in code reviews.

Use instanceof chains sparingly — they signal missing polymorphism. A method that checks instanceof for five different subclasses and calls different methods on each is doing work that belongs in those subclasses. The fix is to move the behaviour into the parent type as an overridden method. Reserve instanceof for cases where you genuinely need a subclass-specific feature that has no meaningful default in the parent.

You never need to null-check before instanceof. The operator returns false for null by design. A guard like if (obj != null && obj instanceof Dog) is redundant — obj instanceof Dog already handles null safely.

Use sealed types with switch pattern matching for exhaustive dispatch. When you own the type hierarchy and every subtype needs different handling, a sealed interface combined with a switch expression gives you compile-time exhaustiveness. Adding a new subtype without handling it becomes a compile error, not a silent runtime gap.

Common Mistakes

Mistake 1 — Redundant Null Check Before instanceof

// Redundant — instanceof already handles null if (obj != null && obj instanceof String s) { System.out.println(s.toUpperCase()); } // Correct and concise if (obj instanceof String s) { System.out.println(s.toUpperCase()); }

The null check adds visual noise and misleads readers into thinking instanceof might throw on null. It never does.

Mistake 2 — Using instanceof Instead of Polymorphism

// Design smell — instanceof chain that should be polymorphism public void processShape(Object shape) { if (shape instanceof Circle c) { System.out.println("Circle area: " + (Math.PI * c.radius * c.radius)); } else if (shape instanceof Rectangle r) { System.out.println("Rectangle area: " + (r.width * r.height)); } else if (shape instanceof Triangle t) { System.out.println("Triangle area: " + (0.5 * t.base * t.height)); } // Adding a new shape means adding another else-if here — fragile } // Better — move area() into each shape class public void processShape(Shape shape) { System.out.println(shape.getClass().getSimpleName() + " area: " + shape.area()); // Adding a new shape requires only a new class — no changes here }

Seniors in product-based company interviews commonly ask: "What would you do if this instanceof chain grew to ten types?" The expected answer is refactoring to polymorphism or, when the type hierarchy is closed, sealed types with switch pattern matching.

Mistake 3 — Testing instanceof on a Compile-Time Incompatible Type

String message = "hello"; // Compile error — String and Integer have no relationship // The compiler knows this can never be true if (message instanceof Integer i) { // error: incompatible types System.out.println(i); }
Compile error: inconvertible types — String cannot be converted to Integer

The compiler rejects instanceof checks between types that have no possible common subtype. This is a useful compile-time catch — unlike ClassCastException which only surfaces at runtime.

Mistake 4 — Forgetting That Pattern Variable Scope Is Limited

Object obj = "DevStackFlow"; if (obj instanceof String s) { System.out.println(s.toUpperCase()); // s is in scope here } // s is not accessible outside the if block // System.out.println(s); // compile error — s cannot be resolved

The pattern variable s is only in scope inside the if block where the pattern matched. Attempting to use it outside causes a compile error. This scoping rule prevents a common bug: assuming a pattern variable is valid in a context where the check may not have run.

Interview Questions

Q1. What does the instanceof operator do in Java?

instanceof is a binary operator that checks whether an object is an instance of a given type at runtime. It returns true if the object's class is the specified type, a subclass of it, or implements the specified interface. It returns false for null without throwing any exception. The compiler also performs a compile-time check — if the two types have no possible relationship in the class hierarchy, the expression is rejected before the program runs.

Q2. What is pattern matching instanceof introduced in Java 16?

Pattern matching for instanceof combines three operations into one expression: the type check, the cast, and the variable binding. Instead of writing if (obj instanceof Dog) { Dog d = (Dog) obj; } on separate lines, you write if (obj instanceof Dog d) — which checks the type, casts it, and binds the result to d in a single step. The variable d is scoped to the block where the pattern matched. This eliminates the redundant explicit cast and is the preferred style in Java 16 and later.

Q3. What is the difference between instanceof and getClass()?

instanceof returns true for the actual type and all supertypes — it is inheritance-aware. getClass() equality returns true only for the exact runtime class — it does not traverse the hierarchy. instanceof also handles null safely, returning false, while getClass() throws NullPointerException on null. In production code, instanceof is the right choice for type checks before casting. getClass() equality is mostly used inside equals() implementations where strict same-class identity is needed.

Q4. Why does instanceof always return false for null?

instanceof is designed to safely handle null by returning false rather than throwing NullPointerException. A null reference has no runtime type — it refers to nothing — so it cannot be an instance of any type. This design allows instanceof to serve as a combined null-and-type check in a single expression, eliminating the need for a separate null guard before checking the type.

Q5. What happens if you use instanceof on types that have no inheritance relationship?

The compiler rejects the expression at compile time with an "incompatible types" error. If the compiler can determine that the cast between two types is impossible — because neither extends the other and there is no interface in common — it will not compile the instanceof check. This is a useful early catch that prevents a class of bugs that would otherwise only appear as ClassCastException at runtime.

Q6. How does instanceof work with sealed types and switch in Java 21?

When a sealed interface or class is used with a switch expression and type patterns, the compiler enforces exhaustiveness — every permitted subtype must have a matching case. If a new subtype is added to the sealed hierarchy without adding a corresponding switch case, the code fails to compile. This guarantees that dispatching logic is never silently incomplete, which is a significant safety improvement over if-else instanceof chains.

FAQs

Can instanceof be used with primitive types in Java?

No. instanceof works only with reference types — classes, interfaces, arrays, and since Java 16, type patterns. Primitives like int, double, and boolean cannot be used with instanceof. To check whether a value is a particular numeric type, use wrapper classes: obj instanceof Integer checks if obj is an Integer reference.

Does instanceof check the reference type or the actual object type?

It always checks the actual runtime object type — not the reference type. If you declare Animal animal = new Dog() and check animal instanceof Dog, the result is true even though the reference is declared as Animal. The reference type only affects what methods the compiler allows you to call; instanceof looks at what the object in memory actually is.

Is it safe to call instanceof on an object without initialising it first?

No. A local variable that has never been assigned any value — not even null — is uninitialized. Using it in any expression, including instanceof, causes a compile error: "variable might not have been initialized." Fields that are not explicitly initialised are set to null by the JVM, so instanceof on an uninitialized field returns false safely.

Can instanceof cause a ClassCastException?

No. instanceof itself never throws any exception — not even for null. A ClassCastException is thrown by an explicit cast (TargetType) object, not by instanceof. The whole purpose of using instanceof before a cast is to prevent the ClassCastException that the cast would throw if the types did not match.

What is the correct way to use instanceof with generics?

Due to type erasure, you cannot use parameterized types directly with instanceof. Writing obj instanceof List<String> causes a compile error because the generic type parameter is erased at runtime and the JVM cannot check it. The correct approach is obj instanceof List<?> or simply obj instanceof List, which checks the raw type. Verifying the element types requires iterating and checking each element individually.

Summary

instanceof answers a single question — is this object compatible with this type — and returns a clean boolean. It traverses the entire inheritance chain upward, handles null safely without throwing, and works on both classes and interfaces. The pattern matching syntax introduced in Java 16 makes it cleaner by combining the check, the cast, and the variable binding into one expression, scoped correctly to the block where the match succeeded.

The most practically important rule: instanceof chains that grow beyond two or three cases usually signal that the type-specific behaviour should move into the class hierarchy as overridden methods. Use it confidently where subclass-specific features genuinely require it. Reach for sealed types and switch patterns when you own a closed hierarchy and need compile-time exhaustiveness guarantees.

For interviews, be ready to explain why instanceof returns false for null, demonstrate the Java 16 pattern matching syntax, describe when getClass() is preferred over instanceof, and explain what the compiler does when you test incompatible types. These come up consistently in both service-based definition rounds and product-based design discussions.

What to Read Next

Practice Programs

Put this topic into practice — each program has full code, output and a step-by-step explanation you can run online.

Browse all Java programs →