Pattern Matching for switch in Java

Pattern matching for `switch` lets Java select a branch by testing a value against type patterns, record patterns, constants, and guarded patterns. Modern `switch` can also be used as an expression, making the matched value directly produce a result.

Traditional `switch` primarily compared a selector with constant case labels. Pattern matching extends this model so a case can test the runtime type of an object and, when matched, bind that object to a variable such as `case String s`.

A pattern case can be combined with additional conditions using a `when` guard, for example `case String s when s.isBlank()`. Pattern matching also works with record patterns, allowing components of a record to be matched and bound directly instead of requiring separate accessors.

Pattern switches have defined dominance rules: a broader pattern must not appear before a more specific pattern if it would make the latter unreachable. For example, `case Object o` dominates `case String s`, so the `String` case must come first.

A `switch` can be an expression. Each matching arm can use `->` and produce a value, while `yield` is available inside a traditional block-style switch expression. Pattern switches can also handle `null` explicitly with `case null`, avoiding the usual null failure of an ordinary switch selector.

Exhaustiveness is important for switch expressions and can be established through a `default`, all relevant enum constants, or suitable sealed-type coverage. With sealed hierarchies, the compiler can recognize that a set of permitted subclasses covers every possible non-null case.

Key points

Interview questions

How do `when` guards work with pattern cases?

A `when` guard adds an additional boolean condition after the pattern has matched. For example, `case Integer i when i > 0` first requires an `Integer`, then requires the value to be positive. If the guard is false, switch processing continues to another applicable case. This is useful when type or structural matching alone is insufficient.

What is pattern matching for `switch` in modern Java?

It allows a switch case to match more than constant values. A type pattern such as `case String s` checks the runtime type and binds the value to `s`. Cases can also use guards and record patterns. This makes type-based branching more concise than repeatedly using `instanceof` and casts.

What is pattern dominance in a switch?

Dominance means an earlier case can make a later case unreachable because the earlier pattern accepts every value the later pattern could accept. For example, `case Object o` dominates `case String s`. The narrower case must therefore appear first. Guards can affect dominance analysis because a guarded pattern is more restrictive than its unguarded form.

How can sealed classes make a pattern switch exhaustive?

A sealed type restricts which classes or interfaces can directly extend or implement it. The compiler can use that permitted-subclass information when checking whether a pattern switch covers every possible value. Therefore, a switch expression over a sealed hierarchy can enumerate the permitted variants without requiring a catch-all `default`. Adding a new permitted subtype can then cause existing exhaustive switches to require an update.

How does `null` behave in a pattern switch?

A type pattern does not match `null`, because `null` has no runtime object type to satisfy the pattern. Modern pattern switches can explicitly handle it with `case null`. If no case handles null, the switch can still fail when given a null selector. Explicitly handling null is often preferable when null is a valid input because it makes the behavior visible in the switch.