in Java 21 with Pattern Matching
Java has come a long way since the days of switch (int)
and switch (String)
. With Java 21, the switch
statement becomes way more expressive thanks to pattern matching — allowing it to work seamlessly with types, records, sealed hierarchies, and more.
In this post, we’ll explore what’s new in switch
, how it improves readability, safety, and power, and see it in action with real examples.
Why Pattern Matching in Switch?
Traditionally, handling polymorphic logic in Java looked something like this:
if (obj instanceof String s) {
System.out.println(s.toUpperCase());
} else if (obj instanceof Integer i) {
System.out.println(i + 1);
}
Now with pattern matching in switch
, you get a cleaner, more concise version:
Object obj = "Hello";
switch (obj) {
case String s -> System.out.println("String: " + s.toUpperCase());
case Integer i -> System.out.println("Integer + 1: " + (i + 1));
case null -> System.out.println("It's null!");
default -> System.out.println("Unknown type");
}
Guards with when
Clauses
Pattern matching also supports guard conditions using when
:
Object obj = 42;
switch (obj) {
case Integer i when i > 100 -> System.out.println("Large integer: " + i);
case Integer i -> System.out.println("Small integer: " + i);
default -> System.out.println("Other type");
}
This allows more nuanced branching without breaking type safety or resorting to deeply nested if
statements.
Records + Sealed Interfaces: A Perfect Match
Let’s bring in some more modern Java tools — records and sealed interfaces — to see the real power of pattern matching.
1. Define a Sealed Interface
sealed interface Notification permits Email, SMS {}
record Email(String from, String subject, String body) implements Notification {}
record SMS(String number, String message) implements Notification {}
2. Handle Logic with Pattern Matching
Notification n = new Email("support@example.com", "Welcome", "Hello!");
String result = switch (n) {
case Email e when e.subject().contains("Welcome") ->
"Send welcome pack to: " + e.from();
case Email e ->
"Archive email from: " + e.from();
case SMS s when s.message().contains("URGENT") ->
"High priority SMS to: " + s.number();
case SMS s ->
"Standard SMS to: " + s.number();
};
The compiler knows Notification
can only be an Email
or SMS
, so the switch is exhaustive — no need for a default case, and the compiler ensures you cover all branches.
Bonus: Enum Matching Still Shines
While switch
on enums isn’t new, you can now use when
with them too:
enum Status { ACTIVE, INACTIVE, ERROR }
Status status = Status.ACTIVE;
String message = switch (status) {
case ACTIVE -> {
if (random) yield "Randomly excited!";
else yield "Just active.";
}
case INACTIVE, ERROR -> "Not working.";
};
Why You Should Care
The new switch
:
- Improves readability: Less boilerplate, fewer casts.
- Boosts safety: Exhaustiveness checks + strong typing.
- Works seamlessly with modern Java features like records and sealed types.
It’s one of those quality-of-life upgrades that make writing Java code less verbose and more expressive — especially for domain modeling.
Conclusion
Java 21’s pattern matching for switch
is a big step forward. Whether you’re working with polymorphic models, building REST APIs, or writing DSLs, this feature will make your code cleaner and safer.
If you haven’t tried it yet — fire up a Java 21 project and give your old switch
statements a makeover.