DEV Community

Rajesh Mishra
Rajesh Mishra

Posted on Originally published at howtostartprogramming.in

Java 21 pattern matching for switch and record patterns — Complete Guide

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 switch syntax 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 case clause.
  • Real‑world refactoring: turning a legacy instanceof chain into a single switch expression.
  • Common pitfalls—such as overlapping patterns and unintended fall‑through—and how to avoid them.
  • Performance considerations and how the JIT treats pattern‑matching switch compared to classic if‑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";
};
}
Enter fullscreen mode Exit fullscreen mode

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 switch eliminates repetitive instanceof + 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 (when clauses) 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)