🚀 Modern Java 17 → 25 · Advanced

Pattern matching for switch in Java

case Circle c ->, guards with when, null cases.

🧩 The mysteryRemember chains of if (o instanceof X) { X x = (X) o; ... } else if ...? Java 21 folds the type test, the cast and the branch into a single switch case.

Switch on types

Since Java 21, switch can match on types. case Integer i -> runs when the value is an Integer and binds it to i, ready to use. Type test, cast and variable, all in one step.

String describe(Object o) {
    return switch (o) {
        case Integer i -> "int " + i;
        case String s  -> "text " + s;
        default        -> "other";
    };
}

Guards with when

Refine a case with a guard: case String s when s.isBlank() ->. It matches only if the type test succeeds and the guard is true. Cases are tried top to bottom; if a guard fails, the later cases still get their chance.

return switch (o) {
    case String s when s.isBlank() -> "blank";
    case String s -> "text";
    default -> "other";
};
🔮 Predict it

Your turn

What does this print?

Object[] items = {5, 12, "", 2.5};
for (Object o : items) {
    String r = switch (o) {
        case Integer i when i > 9 -> "B";
        case Integer i -> "S";
        case String s when s.isEmpty() -> "E";
        default -> "?";
    };
    System.out.print(r + " ");
}
  1. S B E ?
  2. B B E ?
  3. S B ? ?
Show the answer

5 fails the i > 9 guard and falls to the plain Integer case: S. 12 passes: B. "" is an empty String: E. 2.5 is a Double, which reaches default: ?.

⚠️ The trap

default doesn't catch null

default does not match null. A switch with no case null throws NullPointerException when the selector is null. Handle it explicitly, or combine both into one label: case null, default ->.

return switch (o) {
    case null -> "null!";
    case String s -> "text";
    default -> "other";
};
// or: case null, default -> "other";
🔮 Predict it

Order matters

Every Integer is a Number. What happens?

Object o = 7;
String r = switch (o) {
    case Number n -> "number";
    case Integer i -> "int";
    default -> "other";
};
System.out.println(r);
  1. number
  2. int
  3. Compile error
Show the answer

Compile error. The Number case would catch every Integer, so the Integer case could never be reached. The compiler reports it as dominated. Put specific cases before general ones.

💼 In the real world

Goodbye, instanceof chains

Event handlers, message routers and JSON-node walkers used to be long if/else instanceof ladders with casts, which made it easy to forget a branch. A pattern switch states every case side by side, and the compiler catches unreachable ones. It's one of the first things teams adopt after moving to Java 21.

Key takeaways

  1. case Type name -> tests the type and binds in one step
  2. Guards refine a case: case String s when s.isEmpty() ->
  3. Without case null, a null selector throws NullPointerException
  4. Specific cases must come before general ones (dominance)
🤯 Did you know?

Pattern matching for switch went through four preview rounds (Java 17 to 20) before becoming final in Java 21.

Practice questions

What does this print?

Object[] items = {42, "hi", 3.5, 7};
for (Object o : items) {
    String r = switch (o) {
        case Integer i when i > 10 -> "big";
        case Integer i -> "small";
        case String s -> "str";
        default -> "other";
    };
    System.out.print(r + " ");
}
  1. big str other small
  2. big str other big
  3. small str other small
  4. big str str small
Check your answer

big str other small. 42 passes the guard (big), "hi" is a String, 3.5 is a Double so it reaches default, and 7 fails the guard and falls to the plain Integer case.

What does this print?

Object o = null;
String r = switch (o) {
    case String s -> "string";
    default -> "other";
};
System.out.println(r);
  1. Throws NullPointerException
  2. other
  3. string
  4. null
Check your answer

Throws NullPointerException. default does not match null. A switch without case null throws NullPointerException when the selector is null.

Next: don't just match a record, take it apart. Record patterns pull out the components in the same step.