00:00

instanceof Operator in Java – Type Checks and Pattern Matching

The instanceof operator tests whether an object is an instance of a given type: a class, a subclass of it, or a class implementing an interface. It returns a boolean and is most often used before a downcast to make the cast safe.

Modern Java has transformed instanceof: since Java 16 it can declare a variable while testing (pattern matching), and Java 21 adds record patterns and pattern matching for switch. This tutorial covers the classic operator and all of these features.

1. Syntax and Basic Use

Syntax
expression instanceof Type              // classic form: true or false
expression instanceof Type name         // pattern matching (Java 16+)
expression instanceof Record(var a, var b)   // record pattern (Java 21+)
InstanceofBasics.java
import java.util.*;

public class InstanceofBasics {
    static class Animal { }
    static class Dog extends Animal { }
    static class Cat extends Animal { }

    public static void main(String[] args) {
        Animal a = new Dog();
        System.out.println("a instanceof Dog:    " + (a instanceof Dog));     // actual object is a Dog
        System.out.println("a instanceof Animal: " + (a instanceof Animal));  // subclass IS-A parent
        System.out.println("a instanceof Object: " + (a instanceof Object));  // everything is an Object
        System.out.println("a instanceof Cat:    " + (a instanceof Cat));

        List<String> list = new ArrayList<>();
        System.out.println("list instanceof Collection:   " + (list instanceof Collection));
        System.out.println("list instanceof RandomAccess: " + (list instanceof RandomAccess));

        Animal nothing = null;
        System.out.println("null instanceof Animal: " + (nothing instanceof Animal));  // always false
    }
}
Output
a instanceof Dog:    true
a instanceof Animal: true
a instanceof Object: true
a instanceof Cat:    false
list instanceof Collection:   true
list instanceof RandomAccess: true
null instanceof Animal: false
  • instanceof checks the runtime type of the object, not the declared type of the variable.
  • It returns true for the object's class, all its superclasses and all interfaces it implements.
  • null instanceof AnyType is always false, so no null check is needed.
  • If the types can never match (for example String and Integer), the code does not compile.

2. Safe Downcasting

Casting a reference to a subtype throws ClassCastException if the object is not of that type. Testing with instanceof first prevents it. The classic pattern requires a test and a separate cast:

Before Java 16
if (obj instanceof String) {
    String s = (String) obj;      // repeated type, extra cast
    System.out.println(s.length());
}

3. Pattern Matching for instanceof (Java 16)

The pattern form tests and casts in one step, binding a new variable that is in scope only where the test is known to be true:

PatternMatching.java
public class PatternMatching {
    static String describe(Object o) {
        // Java 16+: test and cast in one step; 's', 'i', 'arr' are pattern variables
        if (o instanceof String s && !s.isEmpty()) {
            return "String of length " + s.length();
        } else if (o instanceof Integer i) {
            return "Integer, doubled = " + (i * 2);
        } else if (o instanceof int[] arr) {
            return "int array with " + arr.length + " elements";
        }
        return "something else: " + o;
    }

    public static void main(String[] args) {
        System.out.println(describe("Java"));
        System.out.println(describe(21));
        System.out.println(describe(new int[] {1, 2, 3}));
        System.out.println(describe(""));
        System.out.println(describe(3.14));
    }
}
Output
String of length 4
Integer, doubled = 42
int array with 3 elements
something else: 
something else: 3.14
  • The pattern variable can be used in the rest of a && condition (s.isEmpty() above), but not after ||.
  • With if (!(o instanceof String s)) return;, s is in scope after the if statement (flow scoping).
  • Pattern variables are not implicitly final, but reassigning them is discouraged.

4. Record Patterns (Java 21)

Record patterns go further: they test the type and extract the record's components. This also makes equals implementations much shorter:

OldVsNew.java
public class OldVsNew {
    record Point(int x, int y) { }

    // Before Java 16: test, then cast
    static boolean equalsOld(Point p, Object o) {
        if (!(o instanceof Point)) return false;
        Point other = (Point) o;
        return p.x() == other.x() && p.y() == other.y();
    }

    // Java 16+: pattern matching
    static boolean equalsNew(Point p, Object o) {
        return o instanceof Point other && p.x() == other.x() && p.y() == other.y();
    }

    // Java 21: record patterns deconstruct the object directly
    static String where(Object o) {
        if (o instanceof Point(int x, int y) && x == y) return "on the diagonal at " + x;
        if (o instanceof Point(int x, int y)) return "at (" + x + ", " + y + ")";
        return "not a point";
    }

    public static void main(String[] args) {
        Point p = new Point(2, 3);
        System.out.println(equalsOld(p, new Point(2, 3)) + " " + equalsNew(p, new Point(2, 3)) + " " + equalsNew(p, "x"));
        System.out.println(where(new Point(4, 4)));
        System.out.println(where(p));
        System.out.println(where(42));
    }
}
Output
true true false
on the diagonal at 4
at (2, 3)
not a point

5. Pattern Matching for switch (Java 21)

Long chains of if (x instanceof A) ... else if (x instanceof B) can be replaced by a switch with type patterns. Guards (when) add conditions, and with a sealed type the compiler checks that every case is covered:

SwitchPatterns.java
public class SwitchPatterns {
    sealed interface Payment permits Card, Upi, Cash { }
    record Card(String last4, double amount) implements Payment { }
    record Upi(String vpa, double amount) implements Payment { }
    record Cash(double amount) implements Payment { }

    // Instead of a chain of instanceof checks: pattern matching switch (Java 21)
    static String receipt(Payment p) {
        return switch (p) {
            case Card c when c.amount() > 10_000 -> "Card ****" + c.last4() + " (OTP required)";
            case Card c -> "Card ****" + c.last4();
            case Upi u  -> "UPI " + u.vpa();
            case Cash c -> "Cash " + c.amount();
        };
    }

    public static void main(String[] args) {
        System.out.println(receipt(new Card("4242", 25_000)));
        System.out.println(receipt(new Card("1881", 999)));
        System.out.println(receipt(new Upi("asha@upi", 450)));
        System.out.println(receipt(new Cash(200)));
    }
}
Output
Card ****4242 (OTP required)
Card ****1881
UPI asha@upi
Cash 200.0

6. When to Avoid instanceof

Frequent instanceof checks on your own class hierarchy can be a sign that behaviour belongs in the classes themselves. Consider:

  • Polymorphism: define a method in the parent type and override it in each subclass.
  • Sealed types with switch patterns: when operations are better kept outside the classes, the compiler still guarantees exhaustiveness.

instanceof remains the right tool in equals methods, when handling values of type Object, and when working with types you do not control. See Polymorphism.

7. instanceof vs Class.isInstance() vs getClass()

CheckIncludes subtypes?Type known at
obj instanceof TypeYesCompile time
type.isInstance(obj)YesRun time (a Class object)
obj.getClass() == Type.classNo: exact class onlyCompile time

8. Summary

  • instanceof tests an object's runtime type against a class or interface, including supertypes.
  • It returns false for null and is used to make downcasts safe.
  • Java 16 pattern matching tests and casts in one step; Java 21 adds record patterns and switch patterns.
  • Prefer polymorphism or sealed-type switches over long instanceof chains.

Related Tutorials