Java 21 pattern matching for switch and record patterns — Complete Guide
A practical, in-depth guide to Java 21 pattern matching for switch and record patterns with examples.
INTRO
Every time you write a service that processes heterogeneous data—JSON payloads, domain events, or UI commands—you end up with a cascade of instanceof checks, casts, and nested if‑else blocks. The code quickly becomes a maintenance nightmare: a new subtype means touching every place that performs the type test, and forgetting a cast leads to ClassCastException at runtime.
Java 21 finally gives us a clean, expressive way to de‑structure objects directly inside a switch. Pattern matching for switch lets you combine type tests, null checks, and even record component extraction in a single, exhaustive construct. The result is code that reads like a decision table, eliminates boilerplate, and forces the compiler to verify that all cases are covered.
If you’ve been using the preview features from Java 17‑20 or still rely on the old if (obj instanceof Foo f) pattern, you’ll notice an immediate boost in readability and safety. The new record patterns also mean you can pull apart immutable data carriers without writing a dozen accessor calls. In short, Java 21 turns a noisy, error‑prone workflow into a concise, compile‑time‑checked one.
WHAT YOU'LL LEARN
- How the new
switchsyntax integrates with pattern matching, including guarded patterns and null handling. - The exact rules for exhaustiveness and why the compiler can now warn you about missing cases.
- Record pattern deconstruction: extracting components directly in the
caseclause. - Real‑world refactoring: turning a legacy
instanceofchain into a singleswitchexpression. - Common pitfalls—such as overlapping patterns and unintended fall‑through—and how to avoid them.
- Performance considerations and how the JIT treats pattern‑matching
switchcompared to classicif‑else.
A SHORT CODE SNIPPET
sealed interface Event permits Click, Purchase, Login {}
record Click(int x, int y) implements Event {}
record Purchase(String sku, double amount) implements Event {}
record Login(String user) implements Event {}
static String describe(Event e) {
return switch (e) {
case Click(var x, var y) -> "Clicked at (" + x + ", " + y + ")";
case Purchase(var sku, var amount) when amount > 1000 ->
"High‑value purchase of " + sku + " for $" + amount;
case Purchase(var sku, var amount) -> "Purchase of " + sku + " for $" + amount;
case Login(var user) -> "User " + user + " logged in";
case null -> "No event";
};
}
The snippet shows a sealed hierarchy, record patterns that pull out fields, and a guarded pattern for expensive purchases—all in a single, exhaustive switch.
KEY TAKEAWAYS
- Pattern matching for
switcheliminates repetitiveinstanceof+ cast boilerplate, making type‑based logic declarative. - Record patterns let you destructure immutable data carriers without extra accessor calls, keeping the intent close to the data shape.
- The compiler now enforces exhaustiveness, so you get compile‑time safety for both sealed hierarchies and null handling.
- Guarded patterns (
whenclauses) provide fine‑grained control without sacrificing readability, but overlapping guards can cause subtle bugs if not ordered carefully.
👉 Read the complete guide with step-by-step examples, common mistakes, and production tips:
Java 21 pattern matching for switch and record patterns — Complete Guide
Top comments (0)