When processing heterogeneous collections or API responses, you often need to check types and cast objects repeatedly. instanceof pattern matching combines type checking and casting into a single operation, eliminating redundant code and potential ClassCastException bugs.

Old vs New Style

OldVsNew.java
public class OldVsNew {
    static void oldStyle(Object obj) {
        if (obj instanceof String) {
            String s = (String) obj;  // redundant cast
            System.out.println("OLD: " + s.toUpperCase());
        }
    }

    static void newStyle(Object obj) {
        if (obj instanceof String s) {
            System.out.println("NEW: " + s.toUpperCase());
        }
    }

    public static void main(String[] args) {
        Object obj = "pattern matching";
        oldStyle(obj);
        newStyle(obj);
    }
}

The pattern variable s is automatically cast and scoped to the if block.

pattern_variable A pattern variable is automatically declared, cast, and scoped when the instanceof test succeeds.

Basic Usage

Basic.java
public class Basic {
    public static void main(String[] args) {
        Object obj = ;
        if (obj instanceof String s) {
            System.out.println("Length: " + s.length());
            System.out.println("Upper: " + s.toUpperCase());
        }
    }
}
public class Basic {
    public static void main(String[] args) {
        Object obj = ;
        if (obj instanceof String s) {
            System.out.println("Length: " + s.length());
            System.out.println("Upper: " + s.toUpperCase());
        }
    }
}
public class Basic {
    public static void main(String[] args) {
        Object obj = ;
        if (obj instanceof String s) {
            System.out.println("Length: " + s.length());
            System.out.println("Upper: " + s.toUpperCase());
        }
    }
}

Benefits

  • No redundant cast: type checking and casting happen together
  • Shorter code: fewer lines, clearer intent
  • Safer: compiler ensures the pattern variable is only used where valid

Scope Rules

The pattern variable is in scope where the instanceof test is definitely true:

Scope.java
public class Scope {
    public static void main(String[] args) {
        Object obj = ;
        if (obj instanceof String s) {
            System.out.println("In if: " + s);
        }
        if (obj instanceof String s && s.length() > 3) {
            System.out.println("Length check: " + s);
        }
        if (!(obj instanceof String s)) {
        } else {
            System.out.println("In else: " + s);
        }
    }
}
public class Scope {
    public static void main(String[] args) {
        Object obj = ;
        if (obj instanceof String s) {
            System.out.println("In if: " + s);
        }
        if (obj instanceof String s && s.length() > 3) {
            System.out.println("Length check: " + s);
        }
        if (!(obj instanceof String s)) {
        } else {
            System.out.println("In else: " + s);
        }
    }
}
scope_flow Pattern variables follow "flow scoping" - they are in scope wherever the compiler can prove the pattern matched.

Null Safety

NullSafe.java
public class NullSafe {
    static void process(Object obj) {
        if (obj instanceof String s) {
            System.out.println("String: " + s);
        } else {
            System.out.println("Not a string (or null)");
        }
    }

    public static void main(String[] args) {
        process("hello");
        process(123);
        process(null);  // instanceof is false for null
    }
}

Multiple Type Checks

MultipleTypes.java
public class MultipleTypes {
    static String describe(Object obj) {
        if (obj instanceof String s) {
            return "String of length " + s.length();
        } else if (obj instanceof Integer i) {
            return "Integer value " + i;
        } else if (obj instanceof Double d) {
            return "Double value " + d;
        } else {
            return "Unknown type";
        }
    }

    public static void main(String[] args) {
        System.out.println(describe("hello"));
        System.out.println(describe(42));
        System.out.println(describe(3.14));
        System.out.println(describe(true));
    }
}

Exercise: Practical.java

Create a method that processes a list of Objects and returns formatted strings based on their actual types