Pattern matching for switch
Java 21 pattern matching for switch with type patterns, record patterns, guards, null handling, sealed exhaustiveness, dominance, switch expressions and production failure modes.
On this page
🧩 What is pattern matching for switch?
Pattern matching for switch lets a switch select a branch by the runtime shape of a value, not only by constants. In Java 21 it is final, so case CardPayment c -> ..., case null -> ..., guarded cases with when, and sealed-type exhaustiveness are standard Java. Combined with record patterns, it lets you unpack data in the branch where the match succeeds.
Interviewers ask it because it tests modern Java judgement. You need the syntax, but more importantly you need to know why a sealed switch without default is safer than a long instanceof chain.
What you’ll learn
- Refactor
instanceofchains into Java 21 pattern switch expressions - Use type patterns, record patterns, guards with
whenandcase nullcorrectly - Explain exhaustiveness for sealed hierarchies and why
defaultcan hide future compile errors - Recognise dominance ordering errors and guarded-pattern ordering rules
- Distinguish switch expressions, switch statements and
MatchExceptionin edge cases
For this lesson, we'll cover Java 21 pattern switch, type patterns, record deconstruction patterns, guards with when, case null, exhaustiveness, dominance, switch expressions versus statements and the MatchException edge case.
Below the line (out of scope)
- Primitive patterns are not part of Java 21 final pattern switch.
- Record patterns are Java 21 final through JEP 440, and we will use them with small records.
- Sealed classes themselves are covered in Sealed classes and interfaces.
The 30-second answer
Pattern matching for switch became final in Java 21. It lets a case label contain a type pattern like case CardPayment c, a record pattern like case Refund(OrderId id, Money amount), a guard like case CardPayment c when c.international(), and an explicit case null. For sealed hierarchies, the compiler checks exhaustiveness, so a switch expression over a sealed Payment can omit default if it handles every permitted subtype. Ordering matters because a broad pattern dominates narrower patterns that follow it, so case Payment p before case CardPayment c is a compile error. A switch expression must produce a value for every path; a statement may be used for side effects. MatchException is the rare runtime failure when exhaustive assumptions are invalidated, for example by separate compilation after a sealed hierarchy changes.
Interview radar
Asked very often: How do you replace an instanceof chain with a pattern switch, and how does case null behave?
Senior rounds probe: What is dominance, why should a sealed switch avoid default, and when can MatchException appear?
Production angle: Exhaustive switches turn new domain variants into compile errors instead of hidden runtime fall-throughs.
01Picture it first: a parcel sorting belt
Imagine a sorting belt in a warehouse. Small boxes, fragile tubes and refund envelopes travel on the same belt, and each chute handles a different shape. A guard is the extra sign above the chute: "international cards only". A sealed hierarchy is the manifest that lists every allowed parcel type, so the supervisor can prove every parcel has a chute.
The diagram below maps that warehouse picture to pattern switch cases.
Pattern switch works like a sorting belt where null, guarded card payments, ordinary UPI payments and refund records are routed into separate branches
The key is that the variable exists only inside the chute that matched. case CardPayment c binds c in that arm; it does not leak into the next arm.
02Refactoring an instanceof chain under the hood
Try it yourself first
Before reading on, spot the bug that appears when a new WalletPayment type is added to this code.
static long fee(Payment p) {
if (p instanceof CardPayment c) {
return c.international() ? c.amountPaise() * 3 / 100 : c.amountPaise() * 2 / 100;
}
if (p instanceof UpiPayment) {
return 0;
}
throw new IllegalStateException("unknown payment " + p);
}
The bug is not in today's code. The bug is that tomorrow's type is a runtime surprise. Java 21 lets you make the compiler own that completeness check.
sealed interface Payment permits CardPayment, UpiPayment, WalletPayment { }
record CardPayment(long amountPaise, boolean international) implements Payment { }
record UpiPayment(long amountPaise, String vpa) implements Payment { }
record WalletPayment(long amountPaise, String walletId) implements Payment { }
static long fee(Payment p) {
return switch (p) {
case CardPayment c when c.international() -> c.amountPaise() * 3 / 100;
case CardPayment c -> c.amountPaise() * 2 / 100;
case UpiPayment u -> 0;
case WalletPayment w -> w.amountPaise() / 100;
};
}
Premium lesson
Keep reading with the Java Pass
The rest of this lesson is part of Java One-Stop Interview Prep: in-depth explanations, diagrams, model answers and how to say them in the interview.
- Real world: the null case that changed the incident
- Advanced: dominance, switch forms and MatchException
- How to say it in the interview
From ₹199 · one-time payment · No auto-renewal