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
1// File: InstanceofBasics.java
2
3public class InstanceofBasics {
4
5 static class Animal {}
6 static class Dog extends Animal { public void fetch() { System.out.println("Dog fetches!"); } }
7 static class Cat extends Animal { public void purr() { System.out.println("Cat purrs..."); } }
8
9 public static void main(String[] args) {
10
11 Animal dog = new Dog();
12 Animal cat = new Cat();
13 Animal ref = null;
14
15 // instanceof checks against the actual runtime type, not the reference type
16 System.out.println("dog instanceof Animal : " + (dog instanceof Animal)); // true — Dog IS-A Animal
17 System.out.println("dog instanceof Dog : " + (dog instanceof Dog)); // true — actual type
18 System.out.println("dog instanceof Cat : " + (dog instanceof Cat)); // false — not a Cat
19
20 System.out.println("cat instanceof Dog : " + (cat instanceof Dog)); // false
21 System.out.println("cat instanceof Animal : " + (cat instanceof Animal)); // true
22
23 // null always returns false — never throws NullPointerException
24 System.out.println("null instanceof Animal: " + (ref instanceof Animal)); // false
25 System.out.println("null instanceof Dog : " + (ref instanceof Dog)); // false
26 }
27}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.
1// File: InterfaceInstanceofDemo.java
2
3public class InterfaceInstanceofDemo {
4
5 interface Driveable { void drive(); }
6 interface Chargeable { void charge(); }
7
8 static class Vehicle implements Driveable {
9 @Override public void drive() { System.out.println("Vehicle drives."); }
10 }
11
12 static class ElectricCar extends Vehicle implements Chargeable {
13 @Override public void drive() { System.out.println("Electric car drives silently."); }
14 @Override public void charge() { System.out.println("Electric car charges."); }
15 }
16
17 static class PetrolCar extends Vehicle {
18 @Override public void drive() { System.out.println("Petrol car drives."); }
19 }
20
21 public static void describeVehicle(Vehicle v) {
22 System.out.println("Is Driveable : " + (v instanceof Driveable));
23 System.out.println("Is Chargeable : " + (v instanceof Chargeable));
24 System.out.println("Is ElectricCar: " + (v instanceof ElectricCar));
25 System.out.println("Is PetrolCar : " + (v instanceof PetrolCar));
26 System.out.println();
27 }
28
29 public static void main(String[] args) {
30
31 Vehicle electric = new ElectricCar();
32 Vehicle petrol = new PetrolCar();
33
34 System.out.println("--- ElectricCar ---");
35 describeVehicle(electric);
36
37 System.out.println("--- PetrolCar ---");
38 describeVehicle(petrol);
39
40 // Safe downcast pattern — check before cast
41 if (electric instanceof Chargeable chargeable) {
42 chargeable.charge();
43 }
44 }
45}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.
1// File: PatternMatchingComparison.java
2
3public class PatternMatchingComparison {
4
5 static class Shape {}
6 static class Circle extends Shape { double radius; Circle(double r) { radius = r; } }
7 static class Rectangle extends Shape { double w, h; Rectangle(double w, double h) { this.w=w; this.h=h; } }
8 static class Triangle extends Shape { double b, h; Triangle(double b, double h) { this.b=b; this.h=h; } }
9
10 // Old style — Java 15 and below
11 public static double areaOldStyle(Shape shape) {
12 if (shape instanceof Circle) {
13 Circle c = (Circle) shape; // redundant cast after check
14 return Math.PI * c.radius * c.radius;
15 } else if (shape instanceof Rectangle) {
16 Rectangle r = (Rectangle) shape; // redundant cast
17 return r.w * r.h;
18 } else if (shape instanceof Triangle) {
19 Triangle t = (Triangle) shape; // redundant cast
20 return 0.5 * t.b * t.h;
21 }
22 return 0;
23 }
24
25 // New style — Java 16+ pattern matching
26 public static double areaPatternMatch(Shape shape) {
27 if (shape instanceof Circle c) { // check + cast + bind in one
28 return Math.PI * c.radius * c.radius;
29 } else if (shape instanceof Rectangle r) {
30 return r.w * r.h;
31 } else if (shape instanceof Triangle t) {
32 return 0.5 * t.b * t.h;
33 }
34 return 0;
35 }
36
37 public static void main(String[] args) {
38
39 Shape[] shapes = {
40 new Circle(5),
41 new Rectangle(4, 6),
42 new Triangle(3, 8)
43 };
44
45 for (Shape shape : shapes) {
46 System.out.printf("%-12s area = %.2f%n",
47 shape.getClass().getSimpleName(),
48 areaPatternMatch(shape));
49 }
50 }
51}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.
| Aspect | instanceof | getClass() equality |
|---|---|---|
| Syntax | obj instanceof ClassName | obj.getClass() == ClassName.class |
| Returns | boolean | Class object (needs == comparison) |
| Inheritance aware | Yes — returns true for subclasses | No — exact class match only |
| Null safe | Yes — returns false for null | Throws NullPointerException for null |
| Interface check | Yes | No — interfaces have no instances |
| Used for | Safe type check before downcast | Exact type identity (rare need) |
| Pattern matching | Yes — Java 16+ | No |
equals() best practice | Avoid (too permissive for equality) | Preferred inside equals() for strict type check |
| Common in production | Very common — polymorphism, visitor | Rare — 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.
1// File: InstanceofVsGetClass.java
2
3public class InstanceofVsGetClass {
4
5 static class Animal {}
6 static class Dog extends Animal {}
7
8 public static void main(String[] args) {
9
10 Animal dog = new Dog();
11
12 // instanceof — true for the actual type AND all supertypes
13 System.out.println("instanceof Dog : " + (dog instanceof Dog)); // true
14 System.out.println("instanceof Animal : " + (dog instanceof Animal)); // true
15
16 // getClass() equality — true only for the exact runtime type
17 System.out.println("getClass == Dog : " + (dog.getClass() == Dog.class)); // true
18 System.out.println("getClass == Animal: " + (dog.getClass() == Animal.class)); // false — not exactly Animal
19 }
20}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.
1// File: SwitchPatternDemo.java
2
3public class SwitchPatternDemo {
4
5 sealed interface Notification permits SmsNotification, EmailNotification, PushNotification {}
6 record SmsNotification(String phone, String message) implements Notification {}
7 record EmailNotification(String email, String subject) implements Notification {}
8 record PushNotification(String deviceId, String title) implements Notification {}
9
10 public static String describe(Notification notification) {
11 // switch with type patterns — exhaustive over sealed hierarchy
12 return switch (notification) {
13 case SmsNotification sms -> "SMS to " + sms.phone() + ": " + sms.message();
14 case EmailNotification mail -> "Email to " + mail.email() + " — " + mail.subject();
15 case PushNotification push -> "Push to " + push.deviceId() + ": " + push.title();
16 };
17 }
18
19 public static void main(String[] args) {
20
21 Notification[] notifications = {
22 new SmsNotification("9876543210", "Your OTP is 847201"),
23 new EmailNotification("dev@company.in", "Build pipeline passed"),
24 new PushNotification("device-abc-123", "Order delivered!")
25 };
26
27 for (Notification n : notifications) {
28 System.out.println(describe(n));
29 }
30 }
31}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.
Real-World Example — Payment Processing Dispatcher
The Business Problem
A payment processing service at a platform like PhonePe or Razorpay receives payment events from multiple channels — UPI, net banking, wallet, and card. Each event type carries different fields and needs different validation and logging logic. All events arrive through a common interface for routing. The dispatcher uses instanceof to identify each event type and route it to the appropriate handler without a separate type field.
1// File: PaymentEvent.java
2
3public interface PaymentEvent {
4 String getTransactionId();
5 double getAmount();
6}1// File: UpiEvent.java
2
3public class UpiEvent implements PaymentEvent {
4
5 private final String transactionId;
6 private final double amount;
7 private final String upiHandle;
8 private final String bankRef;
9
10 public UpiEvent(String transactionId, double amount,
11 String upiHandle, String bankRef) {
12 this.transactionId = transactionId;
13 this.amount = amount;
14 this.upiHandle = upiHandle;
15 this.bankRef = bankRef;
16 }
17
18 @Override public String getTransactionId() { return transactionId; }
19 @Override public double getAmount() { return amount; }
20 public String getUpiHandle() { return upiHandle; }
21 public String getBankRef() { return bankRef; }
22}1// File: CardEvent.java
2
3public class CardEvent implements PaymentEvent {
4
5 private final String transactionId;
6 private final double amount;
7 private final String maskedCard;
8 private final String network;
9 private final boolean isInternational;
10
11 public CardEvent(String transactionId, double amount,
12 String maskedCard, String network, boolean isInternational) {
13 this.transactionId = transactionId;
14 this.amount = amount;
15 this.maskedCard = maskedCard;
16 this.network = network;
17 this.isInternational = isInternational;
18 }
19
20 @Override public String getTransactionId() { return transactionId; }
21 @Override public double getAmount() { return amount; }
22 public String getMaskedCard() { return maskedCard; }
23 public String getNetwork() { return network; }
24 public boolean isInternational() { return isInternational; }
25}1// File: WalletEvent.java
2
3public class WalletEvent implements PaymentEvent {
4
5 private final String transactionId;
6 private final double amount;
7 private final String walletProvider;
8 private final double walletBalance;
9
10 public WalletEvent(String transactionId, double amount,
11 String walletProvider, double walletBalance) {
12 this.transactionId = transactionId;
13 this.amount = amount;
14 this.walletProvider = walletProvider;
15 this.walletBalance = walletBalance;
16 }
17
18 @Override public String getTransactionId() { return transactionId; }
19 @Override public double getAmount() { return amount; }
20 public String getWalletProvider() { return walletProvider; }
21 public double getWalletBalance() { return walletBalance; }
22}1// File: PaymentEventDispatcher.java
2
3public class PaymentEventDispatcher {
4
5 public void dispatch(PaymentEvent event) {
6
7 // Common fields available through interface — no instanceof needed
8 System.out.printf("[TXN: %s | Rs.%.2f] ",
9 event.getTransactionId(), event.getAmount());
10
11 // instanceof with pattern matching routes to channel-specific handler
12 if (event instanceof UpiEvent upi) {
13 handleUpiEvent(upi);
14 } else if (event instanceof CardEvent card) {
15 handleCardEvent(card);
16 } else if (event instanceof WalletEvent wallet) {
17 handleWalletEvent(wallet);
18 } else {
19 System.out.println("Unknown event type: " + event.getClass().getSimpleName());
20 }
21 }
22
23 private void handleUpiEvent(UpiEvent event) {
24 System.out.printf("UPI | Handle: %s | BankRef: %s%n",
25 event.getUpiHandle(), event.getBankRef());
26 }
27
28 private void handleCardEvent(CardEvent event) {
29 String scope = event.isInternational() ? "INTERNATIONAL" : "DOMESTIC";
30 System.out.printf("CARD | %s | %s | %s%n",
31 event.getNetwork(), event.getMaskedCard(), scope);
32 }
33
34 private void handleWalletEvent(WalletEvent event) {
35 System.out.printf("WALLET | Provider: %s | Balance after: Rs.%.2f%n",
36 event.getWalletProvider(),
37 event.getWalletBalance() - event.getAmount());
38 }
39}1// File: PaymentDispatcherDemo.java
2
3import java.util.List;
4
5public class PaymentDispatcherDemo {
6
7 public static void main(String[] args) {
8
9 List<PaymentEvent> events = List.of(
10 new UpiEvent("TXN-001", 1499.0, "rahul@upi", "SBIN24011501"),
11 new CardEvent("TXN-002", 8999.0, "**** 4521", "VISA", false),
12 new WalletEvent("TXN-003", 299.0, "Paytm", 1500.0),
13 new CardEvent("TXN-004", 25000.0, "**** 7832", "Mastercard", true),
14 new UpiEvent("TXN-005", 450.0, "priya@iob", "IOB24011502")
15 );
16
17 PaymentEventDispatcher dispatcher = new PaymentEventDispatcher();
18 System.out.println("=== Payment Event Dispatch Log ===\n");
19
20 for (PaymentEvent event : events) {
21 dispatcher.dispatch(event);
22 }
23 }
24}Output:
=== Payment Event Dispatch Log ===
[TXN: TXN-001 | Rs.1499.00] UPI | Handle: rahul@upi | BankRef: SBIN24011501
[TXN: TXN-002 | Rs.8999.00] CARD | VISA | **** 4521 | DOMESTIC
[TXN: TXN-003 | Rs.299.00] WALLET | Provider: Paytm | Balance after: Rs.1201.00
[TXN: TXN-004 | Rs.25000.00] CARD | Mastercard | **** 7832 | INTERNATIONAL
[TXN: TXN-005 | Rs.450.00] UPI | Handle: priya@iob | BankRef: IOB24011502
The common interface gives access to transaction ID and amount without any type checking. Pattern matching instanceof routes each event to the right handler safely, without casting — and without a brittle type field that a caller could forget to set correctly.
instanceof and Inheritance Depth
instanceof traverses the entire inheritance chain, not just one level. Understanding how deep it reaches prevents surprises in larger hierarchies.
1// File: InheritanceDepthDemo.java
2
3public class InheritanceDepthDemo {
4
5 static class A {}
6 static class B extends A {}
7 static class C extends B {}
8
9 public static void main(String[] args) {
10
11 C c = new C();
12
13 // instanceof traverses all levels upward
14 System.out.println("c instanceof C : " + (c instanceof C)); // true — exact type
15 System.out.println("c instanceof B : " + (c instanceof B)); // true — parent
16 System.out.println("c instanceof A : " + (c instanceof A)); // true — grandparent
17
18 Object obj = new C();
19 System.out.println("obj instanceof Object : " + (obj instanceof Object)); // true
20 System.out.println("obj instanceof C : " + (obj instanceof C)); // true
21 System.out.println("obj instanceof B : " + (obj instanceof B)); // true
22 System.out.println("obj instanceof A : " + (obj instanceof A)); // true
23 }
24}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
1// Redundant — instanceof already handles null
2if (obj != null && obj instanceof String s) {
3 System.out.println(s.toUpperCase());
4}
5
6// Correct and concise
7if (obj instanceof String s) {
8 System.out.println(s.toUpperCase());
9}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
1// Design smell — instanceof chain that should be polymorphism
2public void processShape(Object shape) {
3 if (shape instanceof Circle c) {
4 System.out.println("Circle area: " + (Math.PI * c.radius * c.radius));
5 } else if (shape instanceof Rectangle r) {
6 System.out.println("Rectangle area: " + (r.width * r.height));
7 } else if (shape instanceof Triangle t) {
8 System.out.println("Triangle area: " + (0.5 * t.base * t.height));
9 }
10 // Adding a new shape means adding another else-if here — fragile
11}
12
13// Better — move area() into each shape class
14public void processShape(Shape shape) {
15 System.out.println(shape.getClass().getSimpleName() + " area: " + shape.area());
16 // Adding a new shape requires only a new class — no changes here
17}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
1String message = "hello";
2
3// Compile error — String and Integer have no relationship
4// The compiler knows this can never be true
5if (message instanceof Integer i) { // error: incompatible types
6 System.out.println(i);
7}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
1Object obj = "DevStackFlow";
2
3if (obj instanceof String s) {
4 System.out.println(s.toUpperCase()); // s is in scope here
5}
6
7// s is not accessible outside the if block
8// System.out.println(s); // compile error — s cannot be resolvedThe 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
| Topic | Link |
|---|---|
| How upcasting and downcasting work with instanceof for safe type navigation | Java Upcasting and Downcasting → |
| How polymorphism uses runtime dispatch and why instanceof is sometimes the alternative | Java Polymorphism → |
| How inheritance creates the type hierarchy that instanceof traverses | Java Inheritance → |
| How interfaces enable instanceof checks across unrelated class trees | Java Interfaces → |
| How abstract classes define the parent contracts subclasses must implement | Java Abstract Classes → |