skip to content

Switch Pattern Matching

Type patterns in case labels, guards with when, explicit case null, dominance ordering, and exhaustiveness over sealed types. Interviewers ask why a case label is unreachable or where MatchException can still appear at runtime.

part ofJavaoverview, primer and where to startread it →
on this pageshow

questions

5

What is switch pattern matching in Java, and how do type patterns in case labels differ from the traditional constant-based switch?

level: juniorimportance: must knowfreq 60%

answer

  1. case Type var = type pattern, binds + no cast
  2. selector can be any reference type, not just int/enum/String
  3. arrow form = no fallthrough, can be an expression
  4. Java 21 standard (JEP 441)
  5. replaces instanceof if-else ladders

basics

~20 s

Switch pattern matching lets each case test the type of the value instead of comparing it to a constant. A type pattern like case Circle c matches when the value is a Circle and binds it to c, which you can use directly in that branch.

solid answer

~40 s

Traditional switch compares the selector against constant labels (ints, enums, strings) for equality. Switch pattern matching (standardized in Java 21) lets a case label be a type pattern, e.g. case Integer i, which matches when the runtime type of the selector is Integer and binds it to a new variable i scoped to that case. This replaces the old chain of if (x instanceof T t) ... else if ... with a single, readable switch. It works as both a statement and an expression. The selector can be any reference type, not just int/enum/String. Combined with sealed types it gives compiler-checked exhaustiveness, and each arm can further refine the match with guards (when) or destructure records. It is a major step toward data-oriented programming in Java.

code

java · 11 lines
java
sealed interface Shape permits Circle, Square {}
record Circle(double r) implements Shape {}
record Square(double side) implements Shape {}

static double area(Shape s) {
    return switch (s) {           // selector is a reference type
        case Circle c -> Math.PI * c.r() * c.r();  // type pattern binds c
        case Square sq -> sq.side() * sq.side();
        // no default needed: Shape is sealed -> exhaustive
    };
}

go deeper

for a junior

Knows a case label can be a type like case Integer i, that it binds the value with no cast, and that the arrow form has no fallthrough.

for a middle

Explains the selector can be any reference type, distinguishes statement vs expression switch, and knows type patterns don't match null without case null.

for a senior

Frames it against instanceof ladders, ties it to sealed-type exhaustiveness and guards/record patterns, and notes the Java 21 timeline and runtime-type semantics.

for a principal

Positions it within data-oriented programming, weighs pattern switches vs the visitor pattern for extensibility, and reasons about API/library evolution and exhaustiveness guarantees as a design contract.

## The problem it solves A **switch** is a control-flow statement that picks one branch based on a value (the *selector*). The classic switch only allowed **constant** labels: `case 1:`, `case "OPEN":`, `case MONDAY:` — each `case` is an equality test against a compile-time constant. If you wanted to branch on the *type* of an object, you had to write a ladder of `if (x instanceof Circle) { Circle c = (Circle) x; ... } else if (x instanceof Square) { ... }`. This is verbose and error-prone (you cast manually, you can forget a type). ## Type patterns **Pattern matching** (general feature, finalized for `switch` in Java 21, JEP 441) lets a `case` label be a **type pattern** instead of a constant. A type pattern is written `Type variableName`: ```java String describe(Object obj) { return switch (obj) { case Integer i -> "int " + i; // matches if obj is an Integer, binds it to i case String s -> "string " + s.length(); default -> "other"; }; } ``` When the selector's **runtime type** is assignable to the pattern's type, the case matches and the value is bound to the **pattern variable** (`i`, `s`). That variable is already the right type — no cast — and is **scoped to its own case arm**. ### Key terms - **Selector**: the value in `switch (selector)`. With patterns it can be any reference type (e.g. `Object`, an interface), not just `int`/`char`/`enum`/`String`. - **Runtime type**: the actual class of the object at execution time, which may be a subtype of the variable's declared (static) type. - **Pattern variable**: the binding introduced by the pattern (`i` in `case Integer i`). It is effectively final and only in scope where the match is guaranteed. ## Statement vs expression Switch works two ways. As a **statement** it just runs side effects. As a **switch expression** (using the arrow `->` form) it *produces a value*, so you can do `String s = switch (obj) { ... };`. The arrow form also has **no fallthrough** — exactly one arm runs — which removes the classic missing-`break` bug. ## Why it matters Type-pattern switches collapse `instanceof` ladders into one structure, and when the selector type is a **sealed** hierarchy (a closed set of permitted subtypes) the compiler can verify you covered every case (**exhaustiveness**), letting you omit `default`. Arms can be refined with **guards** (`case Integer i when i > 0`) and **record patterns** (destructuring). Together these enable *data-oriented programming*: model data as records + sealed interfaces, then process it with one exhaustive switch. ## Caveats - Available as a standard feature from **Java 21** (preview in 17–20). - A type pattern for a non-nullable case does **not** match `null` — you need an explicit `case null` (covered separately). - Order matters: a more general pattern must not come before a more specific one it would shadow (dominance).

  • Does a type pattern case match null?
    No. A type pattern such as case String s does not match null; passing null falls through to default (if present) or throws NullPointerException when the switch has no null handling. You must add an explicit case null to match it.
  • From which Java version is switch pattern matching a standard (non-preview) feature?
    Java 21 (JEP 441). It was available as a preview in Java 17 through 20.

Old switch is a coat-check that only recognizes ticket numbers; pattern switch is a bouncer who looks at what kind of thing walks in (a Circle? a String?) and hands it to the right room already labeled.

saying these in an interview costs you the question

  • Thinking a plain type pattern like case String s also matches null — it does not.
  • Assuming pattern switch works on primitives or only on int/enum/String selectors; the selector is any reference type.
  • Believing case ordering is free — a general pattern before a specific one is a compile error (dominance).
  • Confusing the arrow form (no fallthrough, can be an expression) with the legacy colon form.

context

open as a page

How does a sealed type hierarchy let a switch expression be exhaustive without a default arm, and why is omitting default sometimes preferable?

level: seniorimportance: must knowfreq 55%

basics

~20 s

A sealed type lists all its permitted subtypes, so the compiler knows the complete set of possibilities. If your switch has an arm for each one, it covers everything and you can skip default. Omitting default means adding a new subtype later causes a compile error, reminding you to handle it.

open as a page

How do you handle null in a switch pattern matching, and what does case null, default mean?

level: middleimportance: should knowfreq 50%

basics

~20 s

A normal pattern case never matches null, so a null selector throws NullPointerException unless you add case null. Writing case null, default means null is handled by the same branch as everything not otherwise matched.

open as a page

What are guarded patterns (case ... when ...) in a switch, and how do they affect matching and exhaustiveness?

level: middleimportance: should knowfreq 48%

basics

~20 s

A guard is a boolean condition added to a case with the when keyword, like case Integer i when i > 0. The arm matches only if the type pattern matches AND the condition is true; otherwise checking continues with the following cases.

open as a page

Explain case-label dominance ordering and the MatchException in pattern switches. When does each arise?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Dominance means a more general case must come after the more specific ones it would otherwise swallow; putting it first is a compile error because later arms become unreachable. MatchException is a runtime error thrown when an exhaustive-looking switch meets a value no arm covers, usually due to mismatched separate compilation.

open as a page