When handling multiple object types in event handlers, parsers, or command dispatchers, cascading if-else chains become unwieldy. Switch pattern matching lets you dispatch on types elegantly, with automatic casting and compiler-verified exhaustiveness.

Basic Type Patterns

Basic.java
public class Basic {
    public static void main(String[] args) {
        Object obj = ;
        switch (obj) {
            case String s -> System.out.println("String: " + s.toUpperCase());
            case Integer i -> System.out.println("Integer: " + i * 2);
            case Double d -> System.out.println("Double: " + d / 2);
            default -> System.out.println("Unknown type");
        }
    }
}
public class Basic {
    public static void main(String[] args) {
        Object obj = ;
        switch (obj) {
            case String s -> System.out.println("String: " + s.toUpperCase());
            case Integer i -> System.out.println("Integer: " + i * 2);
            case Double d -> System.out.println("Double: " + d / 2);
            default -> System.out.println("Unknown type");
        }
    }
}
public class Basic {
    public static void main(String[] args) {
        Object obj = ;
        switch (obj) {
            case String s -> System.out.println("String: " + s.toUpperCase());
            case Integer i -> System.out.println("Integer: " + i * 2);
            case Double d -> System.out.println("Double: " + d / 2);
            default -> System.out.println("Unknown type");
        }
    }
}
public class Basic {
    public static void main(String[] args) {
        Object obj = ;
        switch (obj) {
            case String s -> System.out.println("String: " + s.toUpperCase());
            case Integer i -> System.out.println("Integer: " + i * 2);
            case Double d -> System.out.println("Double: " + d / 2);
            default -> System.out.println("Unknown type");
        }
    }
}
type_pattern A type pattern in switch matches the runtime type and binds a pattern variable in one step.

Switch Expressions with Patterns

Expression.java
public class Expression {
    static String describe(Object obj) {
        return switch (obj) {
            case String s -> "String of length " + s.length();
            case Integer i -> "Integer: " + i;
            case Double d -> "Double: " + d;
            case null -> "null value";
            default -> "Unknown type: " + obj.getClass().getSimpleName();
        };
    }

    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(null));
        System.out.println(describe(true));
    }
}

Benefits

  • Type testing: match on types, not just constants
  • Pattern variables: automatic casting in each case
  • Null handling: case null is explicit
  • Exhaustiveness: compiler checks all cases are covered

Null Handling

Null.java
public class Null {
    static void process(Object obj) {
        switch (obj) {
            case null -> System.out.println("Got null");
            case String s -> System.out.println("String: " + s);
            case Integer i -> System.out.println("Integer: " + i);
            default -> System.out.println("Other: " + obj);
        }
    }

    public static void main(String[] args) {
        process("test");
        process(123);
        process(null);  // handled explicitly
    }
}
null_case The explicit `case null` pattern allows safe null handling without NullPointerException.

Case Ordering

Ordering.java
public class Ordering {
    static void check(Object obj) {
        switch (obj) {
            case String s when s.isEmpty() -> System.out.println("Empty string");
            case String s -> System.out.println("Non-empty string: " + s);
            case Integer i when i < 0 -> System.out.println("Negative");
            case Integer i -> System.out.println("Non-negative: " + i);
            case null -> System.out.println("null");
            default -> System.out.println("Other");
        }
    }

    public static void main(String[] args) {
        check("");
        check("hello");
        check(-5);
        check(10);
        check(null);
    }
}
dominance Case ordering matters - more specific patterns must come before more general ones to avoid dominance errors.

Exhaustiveness Checking

Exhaustiveness.java
public class Exhaustiveness {
    sealed interface Shape permits Circle, Rectangle {}
    record Circle(double radius) implements Shape {}
    record Rectangle(double width, double height) implements Shape {}

    static double area(Shape shape) {
        return switch (shape) {
            case Circle c -> Math.PI * c.radius() * c.radius();
            case Rectangle r -> r.width() * r.height();
        };
    }

    public static void main(String[] args) {
        Shape circle = new Circle(5.0);
        Shape rect = new Rectangle(4.0, 6.0);

        System.out.println("Circle area: " + area(circle));
        System.out.println("Rectangle area: " + area(rect));
    }
}
exhaustiveness The compiler verifies all possible types are handled, requiring either complete coverage or a default case.

Exercise: Practical.java

Build a shape area calculator that handles Circle, Rectangle, and Triangle types using switch pattern matching