Instanceof Operator
The instanceof operator tests, at runtime, whether an object is an instance of a given type: a class, a subclass of it, or an interface it implements. It returns true or false. Since Java 16, instanceof can also bind a variable of the tested type in the same step, which removes the need for a separate cast. It is the standard way to safely work with an object whose exact type is not known at compile time.
Basic Syntax
object instanceof Type
Object value = "hello";
System.out.println(value instanceof String); // true
System.out.println(value instanceof Object); // true: every object is an Object
System.out.println(value instanceof Integer); // false
System.out.println(value instanceof CharSequence); // true: String implements CharSequence
The test is based on the object's actual runtime class, not on the declared type of the variable. value is declared as Object, but the object it refers to is a String.
instanceof and Inheritance
instanceof is true for the object's own class and for every supertype above it:
class Vehicle { }
class Car extends Vehicle { }
class ElectricCar extends Car { }
Vehicle v = new ElectricCar();
System.out.println(v instanceof ElectricCar); // true
System.out.println(v instanceof Car); // true
System.out.println(v instanceof Vehicle); // true
System.out.println(v instanceof Object); // true
An ElectricCar is a Car and is a Vehicle, so all of these hold. The test answers "can this object be treated as this type?", not "is its class exactly this type?". To check the exact class, compare getClass():
System.out.println(v.getClass() == Car.class); // false
System.out.println(v.getClass() == ElectricCar.class); // true
null Is Never an Instance
instanceof always returns false for null, without throwing:
String s = null;
System.out.println(s instanceof String); // false
This makes instanceof a built-in null check. if (x instanceof Foo) never needs a separate x != null test.
Compile-Time Check
The compiler rejects an instanceof test that can never be true:
String text = "abc";
System.out.println(text instanceof Integer); // compile error: incompatible types
A String variable can only ever hold a String (a final class), and String and Integer are unrelated, so the test is pointless and the compiler reports it. Tests against interfaces are usually allowed, because some subclass might implement the interface, unless the declared class is final and does not implement it.
Using instanceof Before a Cast
instanceof exists mainly to make downcasting safe. A cast to the wrong type throws ClassCastException at runtime:
Object o = 42;
String s = (String) o; // ClassCastException: Integer cannot be cast to String
The traditional pattern checks first, then casts:
if (o instanceof String) {
String s = (String) o;
System.out.println(s.length());
}
The type name appears twice, and a copy-paste mistake could test one type and cast to another.
Pattern Matching for instanceof (Java 16+)
Modern Java combines the test, the cast, and a new variable in one expression:
if (o instanceof String s) {
System.out.println(s.length()); // s is already a String
}
String s is a type pattern. If the test succeeds, s is declared and assigned the object, already typed as String. If the test fails, s does not exist.
Where the pattern variable is in scope
The variable is available wherever the compiler can prove the test succeeded:
if (o instanceof String s && s.length() > 3) { // usable after &&
System.out.println(s.toUpperCase());
}
if (!(o instanceof String s)) {
return; // leave if not a String
}
System.out.println(s.strip()); // usable here: we only get here if it matched
The second form, an early return when the test fails, is common for guarding the rest of a method.
It is not available where the test may have failed:
if (o instanceof String s || s.isEmpty()) { } // compile error: s may not be assigned
With ||, the right side runs exactly when the left side is false, so s would not exist there.
In equals methods
Pattern matching makes the standard equals implementation shorter and safer:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Point other)) return false;
return x == other.x && y == other.y;
}
The instanceof check also handles null, since null instanceof Point is false.
Record Patterns (Java 21+)
For records, a pattern can also take the object apart into its components:
record Point(int x, int y) { }
Object obj = new Point(3, 4);
if (obj instanceof Point(int x, int y)) {
System.out.println("x=" + x + ", y=" + y); // x=3, y=4
}
Point(int x, int y) matches a Point and extracts its components into new variables, calling the record's accessors behind the scenes. Record patterns can nest:
record Line(Point start, Point end) { }
if (obj instanceof Line(Point(var x1, var y1), Point(var x2, var y2))) {
double length = Math.hypot(x2 - x1, y2 - y1);
}
var lets the compiler infer each component's type.
instanceof in switch (Java 21+)
When testing one object against several types, a pattern-matching switch is clearer than a chain of instanceof checks:
static String describe(Object o) {
if (o instanceof Integer i) return "int " + i;
else if (o instanceof String s) return "text of length " + s.length();
else if (o instanceof int[] arr) return "array of " + arr.length;
else return "other";
}
static String describeSwitch(Object o) {
return switch (o) {
case Integer i -> "int " + i;
case String s -> "text of length " + s.length();
case int[] arr -> "array of " + arr.length;
case null -> "null";
default -> "other";
};
}
Both work. The switch version reads as a table, can include when guards, and, for sealed hierarchies, lets the compiler verify that every subtype is handled.
When to Use instanceof, and When Not To
instanceof is the right tool when:
- Implementing
equals(Object). - Handling values from an untyped source: deserialized data,
Objectparameters in older APIs, heterogeneous collections, reflection. - Deconstructing data types such as records, especially in a sealed hierarchy.
- Checking for an optional capability:
if (resource instanceof AutoCloseable c) c.close();
It is often a warning sign when it is used to decide behavior based on type:
// Fragile: every new shape requires editing this method
double area(Shape s) {
if (s instanceof Circle c) return Math.PI * c.r() * c.r();
if (s instanceof Square q) return q.side() * q.side();
throw new IllegalArgumentException("unknown shape");
}
In a class hierarchy designed for extension, put the behavior in the classes with an overridden method (s.area()), and let polymorphism choose. With sealed types and pattern-matching switch, the type-switching style becomes safe, because the compiler reports any missing case. The choice depends on whether new types or new operations are more likely to be added.
Practical Example: Processing Mixed Events
import java.util.List;
public class EventLog {
sealed interface Event permits Login, Purchase, Error { }
record Login(String user) implements Event { }
record Purchase(String user, double amount) implements Event { }
record Error(String code, String message) implements Event { }
static double revenue(List<Object> rawEvents) {
double total = 0;
for (Object raw : rawEvents) {
if (!(raw instanceof Event e)) {
System.out.println("Skipping unknown item: " + raw);
continue;
}
if (e instanceof Purchase(String user, double amount) && amount > 0) {
total += amount;
}
}
return total;
}
static String summarize(Event e) {
return switch (e) {
case Login l -> l.user() + " logged in";
case Purchase(String user, double amount) when amount >= 10_000 ->
user + " made a LARGE purchase of " + amount;
case Purchase p -> p.user() + " bought " + p.amount();
case Error err -> "ERROR " + err.code() + ": " + err.message();
};
}
public static void main(String[] args) {
List<Object> raw = List.of(
new Login("ada"),
new Purchase("ada", 2499),
"corrupted-line",
new Purchase("linus", 15000),
new Error("E42", "Payment gateway timeout")
);
System.out.println("Revenue: " + revenue(raw));
for (Object o : raw) {
if (o instanceof Event e) {
System.out.println(summarize(e));
}
}
}
}
Output:
Skipping unknown item: corrupted-line
Revenue: 17499.0
ada logged in
ada bought 2499.0
linus made a LARGE purchase of 15000.0
ERROR E42: Payment gateway timeout
The input list holds Object values, as data read from a queue or file often does. raw instanceof Event e filters out anything that is not an event and binds the rest to a typed variable; because instanceof is false for null, nulls would be skipped too. A record pattern extracts amount from purchases in a single condition. In summarize, the switch needs no default: Event is sealed, so the compiler knows the three possible types and would reject the switch if one were missing. The guarded Purchase case must come before the unguarded one, since cases are checked from top to bottom.