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

obj
Basic.java
Replay: real traced execution (multi-file project)
public class Basic {
    public static void main(String[] args) {
        Object obj = "hello";
        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 = 42;
        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 = 3.14;
        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 = true;
        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");
        }
    }
}
  1. obj ← hello

    1public class Basic {2    public static void main(String[] args) {3        Object obj→ hello = "hello"; //@obj="hello", 42, 3.14, true4        switch (obj) {
  1. obj ← 42

    1public class Basic {2    public static void main(String[] args) {3        Object obj→ 42 = 42;4        switch (obj) {
  1. obj ← 3.14

    1public class Basic {2    public static void main(String[] args) {3        Object obj→ 3.14 = 3.14;4        switch (obj) {
  1. obj ← true

    1public class Basic {2    public static void main(String[] args) {3        Object obj→ true = true;4        switch (obj) {
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
Replay: real traced execution (multi-file project)
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));
    }
}
  1. public static void main(String[] args)

    12public static void main(String[] args) {13    System.out.println(describe("hello"));14    System.out.println(describe(42));
  2. static String describe(Object obj)

    pass 1 of 5
    1public class Expression {2    static String describe(Object objhello) {3        return switch (obj) {4            case String s -> "String of length " + s.length();5            case Integer i -> "Integer: " + i;6            case Double d -> "Double: " + d;7            case null -> "null value";8            default -> "Unknown type: " + obj.getClass().getSimpleName();9        };10    }
    All 5 passes — pass 1 is the card above
    passobj
    1hello
    242
    33.14
    4null
    5true
  3. System.out.println(describe("hello"));

    12public static void main(String[] args) {13    System.out.println(describe("hello"));14    System.out.println(describe(42));15    System.out.println(describe(3.14));
    outputString of length 5
  4. System.out.println(describe(42));

    13System.out.println(describe("hello"));14System.out.println(describe(42));15System.out.println(describe(3.14));16System.out.println(describe(null));
    outputInteger: 42
  5. System.out.println(describe(3.14));

    14System.out.println(describe(42));15System.out.println(describe(3.14));16System.out.println(describe(null));17System.out.println(describe(true));
    outputDouble: 3.14
  6. System.out.println(describe(null));

    15    System.out.println(describe(3.14));16    System.out.println(describe(null));17    System.out.println(describe(true));18}
    outputnull value
  7. System.out.println(describe(true));

    16    System.out.println(describe(null));17    System.out.println(describe(true));18}
    outputUnknown type: Boolean

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
Replay: real traced execution (multi-file project)
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
    }
}
  1. public static void main(String[] args)

    11public static void main(String[] args) {12    process("test");13    process(123);
  2. static void process(Object obj)

    pass 1 of 3
    1public class Null {2    static void process(Object objtest) {3        switch (obj) {
    All 3 passes — pass 1 is the card above
    passobj
    1test
    2123
    3null
  3. process("test");

    11public static void main(String[] args) {12    process("test");13    process(123);14    process(null);  // handled explicitly
  4. process(123);

    12    process("test");13    process(123);14    process(null);  // handled explicitly15}
  5. process(null); // handled explicitly

    13    process(123);14    process(null);  // handled explicitly15}
null_case The explicit `case null` pattern allows safe null handling without NullPointerException.

Case Ordering

Ordering.java
Replay: real traced execution (multi-file project)
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);
    }
}
  1. public static void main(String[] args)

    13public static void main(String[] args) {14    check("");15    check("hello");
  2. static void check(Object obj)

    pass 1 of 5
    1public class Ordering {2    static void check(Object obj(empty)) {3        switch (obj) {
    All 5 passes — pass 1 is the card above
    passobj
    1(empty)
    2hello
    3-5
    410
    5null
  3. check("");

    13public static void main(String[] args) {14    check("");15    check("hello");16    check(-5);
  4. check("hello");

    14check("");15check("hello");16check(-5);17check(10);
  5. check(-5);

    15check("hello");16check(-5);17check(10);18check(null);
  6. check(10);

    16    check(-5);17    check(10);18    check(null);19}
  7. check(null);

    17    check(10);18    check(null);19}
dominance Case ordering matters - more specific patterns must come before more general ones to avoid dominance errors.

Exhaustiveness Checking

Exhaustiveness.java
Replay: real traced execution (multi-file project)
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));
    }
}
  1. circle ← Circle[radius=5.0], rect ← Rectangle[width=4.0, height=6.0]

    13public static void main(String[] args) {14    Shape circle→ Circle[radius=5.0] = new Circle(5.0);15    Shape rect→ Rectangle[width=4.0, height=6.0] = new Rectangle(4.0, 6.0);1617    System.out.println("Circle area: " + area(circleCircle[radius=5.0]));18    System.out.println("Rectangle area: " + area(rect));
  2. static double area(Shape shape)

    pass 1 of 2
    6static double area(Shape shapeCircle[radius=5.0]) {7    return switch (shape) {8        case Circle c -> Math.PI * c.radius() * c.radius();9        case Rectangle r -> r.width() * r.height();10    };11}
  3. System.out.println("Circle area: " + area(circle));

    17    System.out.println("Circle area: " + area(circleCircle[radius=5.0]));18    System.out.println("Rectangle area: " + area(rectRectangle[width=4.0, height=6.0]));19}
    outputCircle area: 78.53981633974483
  4. static double area(Shape shape)

    pass 2 of 2
    6static double area(Shape shapeRectangle[width=4.0, height=6.0]) {7    return switch (shape) {8        case Circle c -> Math.PI * c.radius() * c.radius();9        case Rectangle r -> r.width() * r.height();10    };11}
  5. System.out.println("Rectangle area: " + area(rect));

    17    System.out.println("Circle area: " + area(circle));18    System.out.println("Rectangle area: " + area(rectRectangle[width=4.0, height=6.0]));19}
    outputRectangle area: 24.0
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