⚡IntermediateModern Java

instanceof Pattern Matching

Use instanceof with pattern matching (Java 16+) to eliminate redundant casts and write cleaner type checks.

Overview

Before Java 16, checking a type and using the value required two steps: the instanceof check and an explicit cast. Java 16 introduced pattern matching for instanceof. With if (obj instanceof String s), both the type check and the binding to a new variable s happen in one expression. If the check passes, s is in scope inside the if block without any cast needed. Java 21 extended this to switch expressions with pattern matching, allowing you to match multiple types in a single switch.

Key Points

  • •if (obj instanceof TypeName varName) eliminates the separate cast
  • •varName is in scope in the if body and can also be in scope in &&/|| conditions
  • •The pattern variable is effectively final within its scope
  • •Pattern matching in switch (Java 21): case TypeName varName ->
  • •Guarded patterns: case TypeName t when t.someCondition() ->
  • •Pattern matching with null: case null -> in a switch
  • •Reduces ClassCastException risk: casting only happens after a successful instanceof check

Examples

Basic instanceof pattern matching

public class Main {
    sealed interface Shape permits Circle, Rectangle, Triangle {}
    record Circle(double radius) implements Shape {}
    record Rectangle(double width, double height) implements Shape {}
    record Triangle(double base, double height) implements Shape {}

    static double area(Shape shape) {
        // Old way:
        // if (shape instanceof Circle) { Circle c = (Circle) shape; return Math.PI * c.radius() * c.radius(); }

        // New way with pattern matching:
        if (shape instanceof Circle c) {
            return Math.PI * c.radius() * c.radius();
        } else if (shape instanceof Rectangle r) {
            return r.width() * r.height();
        } else if (shape instanceof Triangle t) {
            return 0.5 * t.base() * t.height();
        }
        return 0;
    }

    public static void main(String[] args) {
        Shape[] shapes = {
            new Circle(5),
            new Rectangle(4, 6),
            new Triangle(3, 8)
        };
        for (var s : shapes) {
            System.out.printf("Area of %s: %.2f%n", s.getClass().getSimpleName(), area(s));
        }
    }
}

With pattern matching, you get c, r, t as properly-typed variables inside each branch. No explicit cast. If the instanceof check fails, the variable is not in scope.

Pattern matching in switch (Java 21)

public class Main {
    sealed interface Notification permits Email, SMS, Push {}
    record Email(String to, String subject) implements Notification {}
    record SMS(String phone, String text) implements Notification {}
    record Push(String deviceId, String title) implements Notification {}

    static String format(Notification n) {
        return switch (n) {
            case Email e -> "Email to " + e.to() + ": " + e.subject();
            case SMS s   -> "SMS to " + s.phone() + ": " + s.text();
            case Push p  -> "Push to device " + p.deviceId() + ": " + p.title();
        };
    }

    public static void main(String[] args) {
        Notification[] notifications = {
            new Email("alice@ex.com", "Hello"),
            new SMS("+1234567890", "Your code is 1234"),
            new Push("device-abc", "New message")
        };
        for (var n : notifications) {
            System.out.println(format(n));
        }
    }
}

Pattern matching in switch is exhaustive for sealed types: the compiler ensures all subtypes are covered. No default is needed when all cases are listed.

Guarded patterns and pattern matching with conditions

public class Main {
    static void describe(Object obj) {
        // Pattern matching with &&: condition in the if
        if (obj instanceof String s && s.length() > 5) {
            System.out.println("Long string: " + s.toUpperCase());
        } else if (obj instanceof String s) {
            System.out.println("Short string: " + s);
        } else if (obj instanceof Integer i && i > 0) {
            System.out.println("Positive int: " + i);
        } else if (obj instanceof Integer i) {
            System.out.println("Non-positive int: " + i);
        } else {
            System.out.println("Other: " + obj);
        }
    }

    static String classify(Object obj) {
        // Guarded patterns in switch (Java 21)
        return switch (obj) {
            case Integer i when i < 0 -> "negative int: " + i;
            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;
            case null -> "null";
            default -> "other: " + obj;
        };
    }

    public static void main(String[] args) {
        Object[] values = {"hello", "hi", 42, -5, 0, 3.14, null};
        for (var v : values) {
            describe(v);
        }
        System.out.println("---");
        for (var v : values) {
            System.out.println(classify(v));
        }
    }
}

Guarded patterns (when clause) allow additional conditions within a pattern match case. In if statements, use && after the instanceof pattern to add conditions.

Common Mistakes

  • Using the old cast pattern when instanceof is already available. if (obj instanceof String) { String s = (String) obj; ... } can be simplified to if (obj instanceof String s) { ... }
  • Forgetting that the pattern variable is only in scope when the pattern matches. You cannot use s outside the if block or in the else branch
  • Confusing guarded patterns (when) with the old-style separate condition. case String s when s.isEmpty() is the correct syntax for conditional patterns in switch

Note

Combine instanceof pattern matching with sealed interfaces/classes for exhaustive pattern matching. The compiler can verify that all cases are covered, eliminating default branches and catching missed cases at compile time.

Exercises

Write Java code and click Run to execute it instantly. When your output looks correct, mark as done.

Loading editor…

Completed: 0 / 4 exercises

Knowledge Check

Question 1 of 4

What does "if (obj instanceof String s)" do compared to "if (obj instanceof String)"?