☕ Java

instanceof Operator

instanceof checks whether an object is an instance of a specific class or interface. It's the safe way to determine an object's type at runtime before casting. Java 16 added pattern matching to instanceof — eliminating the redundant cast that always followed it.

Basic instanceof

instanceof takes an object reference on the left and a type on the right. It returns true if the object is an instance of that type — or a subtype of it.
Java
// Basic syntax: object instanceof Type
Object obj = "Hello";

System.out.println(obj instanceof String);   // true
System.out.println(obj instanceof Object);   // trueString IS-A Object
System.out.println(obj instanceof Integer);  // false

// With class hierarchy:
class Animal { }
class Dog extends Animal { }
class Cat extends Animal { }

Dog dog = new Dog();
System.out.println(dog instanceof Dog);     // true
System.out.println(dog instanceof Animal);  // true — Dog IS-A Animal
System.out.println(dog instanceof Object);  // true — everything IS-A Object
System.out.println(dog instanceof Cat);     // false

// With interfaces:
interface Flyable { }
class Bird extends Animal implements Flyable { }

Bird bird = new Bird();
System.out.println(bird instanceof Flyable);  // trueimplements Flyable
System.out.println(bird instanceof Animal);   // trueextends Animal

null and instanceof

instanceof always returns false when the left operand is null. This is one of the most useful properties — it means instanceof is inherently null-safe.
Java
// null instanceof anything = false (always):
String s = null;
System.out.println(s instanceof String);   // false — not true
System.out.println(s instanceof Object);   // false

Object o = null;
System.out.println(o instanceof String);   // false

// This makes instanceof null-safe:
// Traditional null-then-cast pattern required null check:
if (obj != null && obj instanceof String) {
    String str = (String) obj;
    System.out.println(str.length());
}

// With instanceof, null check is redundant:
if (obj instanceof String) {   // false if obj is null — no NPE
    String str = (String) obj;
    System.out.println(str.length());
}

instanceof Before Casting — The Classic Pattern

The traditional use of instanceof is to guard a downcast. Without instanceof, a wrong cast throws ClassCastException at runtime. With instanceof, you confirm the type before casting safely.
Java
// Without instanceof — risky:
void process(Animal animal) {
    Dog dog = (Dog) animal;   // throws ClassCastException if animal is a Cat
    dog.fetch();
}

// With instanceof — safe:
void process(Animal animal) {
    if (animal instanceof Dog) {
        Dog dog = (Dog) animal;   // safe — confirmed above
        dog.fetch();
    } else if (animal instanceof Cat) {
        Cat cat = (Cat) animal;
        cat.purr();
    }
}

// The classic anti-pattern — instanceof + getClass() comparison:
// instanceof checks the full hierarchy (includes subclasses)
// getClass() checks exact type only

class Square extends Rectangle { }

Rectangle r = new Square();
System.out.println(r instanceof Rectangle);         // true — Square IS-A Rectangle
System.out.println(r.getClass() == Rectangle.class); // false — exact type is Square

// Use instanceof for "is compatible with" checks
// Use getClass() equality for "is exactly this type" checks (rare)

Pattern Matching instanceof (Java 16+)

Java 16 introduced pattern matching for instanceof. Instead of checking the type and then immediately casting to a new variable, you can do both in one step. The cast variable is only in scope when the condition is true.
Java
// Before Java 16 — check then cast (redundant):
if (obj instanceof String) {
    String s = (String) obj;    // redundant cast — we already know it's a String
    System.out.println(s.toUpperCase());
}

// Java 16+ pattern matching — check and bind in one step:
if (obj instanceof String s) {
    System.out.println(s.toUpperCase());   // s is already a String — no cast needed
}

// The binding variable s is only in scope inside the if block:
if (obj instanceof String s) {
    System.out.println(s.length());   // s available here
}
// s not available here

// Works with the logical operators:
if (obj instanceof String s && s.length() > 5) {
    System.out.println("Long string: " + s);
}
// s available in both the && condition AND the body

// Negation — s is NOT in scope in the negated branch:
if (!(obj instanceof String s)) {
    return;   // s not available here
}
System.out.println(s.length());  // s IS available here — after the early return

// Pattern matching in complex type-switching:
void describe(Object obj) {
    if (obj instanceof Integer i) {
        System.out.println("Integer: " + i);
    } else if (obj instanceof String s) {
        System.out.println("String of length " + s.length());
    } else if (obj instanceof int[] arr) {
        System.out.println("int array of length " + arr.length);
    } else {
        System.out.println("Unknown: " + obj);
    }
}

Pattern Matching in switch (Java 21+)

Java 21 extends pattern matching to switch expressions and statements — enabling concise, exhaustive type-dispatching without any instanceof chains.
Java
// Java 21switch with type patterns:
Object obj = getValue();

String result = switch (obj) {
    case Integer i  -> "Integer: " + i;
    case Long l     -> "Long: " + l;
    case Double d   -> "Double: " + d;
    case String s   -> "String: " + s.toUpperCase();
    case int[] arr  -> "int array, length " + arr.length;
    case null       -> "null value";
    default         -> "Other: " + obj.getClass().getSimpleName();
};
System.out.println(result);

// Guarded patterns — add conditions to cases with 'when':
String classify(Object obj) {
    return switch (obj) {
        case Integer i when i < 0  -> "negative int";
        case Integer i when i == 0 -> "zero";
        case Integer i             -> "positive int: " + i;
        case String s when s.isEmpty() -> "empty string";
        case String s              -> "string: " + s;
        default                    -> "other";
    };
}

// With sealed classes — switch can be exhaustive (no default needed):
sealed interface Shape permits Circle, Rectangle, Triangle { }
record Circle(double radius) implements Shape { }
record Rectangle(double w, double h) implements Shape { }
record Triangle(double base, double height) implements Shape { }

double area(Shape shape) {
    return switch (shape) {
        case Circle c      -> Math.PI * c.radius() * c.radius();
        case Rectangle r   -> r.w() * r.h();
        case Triangle t    -> 0.5 * t.base() * t.height();
        // no default needed — sealed hierarchy is exhaustive
    };
}

instanceof vs getClass() vs isInstance()

Java provides three ways to check an object's type at runtime. Each has different semantics and appropriate use cases.
Java
class Animal { }
class Dog extends Animal { }

Dog dog = new Dog();

// 1. instanceof — checks if object IS-A Type (includes subclasses):
System.out.println(dog instanceof Animal);           // true
System.out.println(dog instanceof Dog);              // true

// 2. getClass() == — checks EXACT runtime type only:
System.out.println(dog.getClass() == Animal.class);  // false — Dog != Animal
System.out.println(dog.getClass() == Dog.class);     // true  — exact match

// 3. Class.isInstance() — runtime equivalent of instanceof (useful in generics):
Class<?> type = String.class;
System.out.println(type.isInstance("Hello"));   // true
System.out.println(type.isInstance(42));        // false

// When to use each:
// instanceof"is this object compatible with this type?" (most common)
// getClass() ==    — "is this object exactly this type, not a subtype?" (rare)
// Class.isInstance — "check instanceof dynamically at runtime in generic code"

// Example where the difference matters:
void printIfAnimal(Object o) {
    if (o instanceof Animal) {
        System.out.println("Is an animal (or subtype)");  // Dog qualifies
    }
    if (o.getClass() == Animal.class) {
        System.out.println("Is exactly an Animal");  // Dog does NOT qualify
    }
}