Java 21 Pattern Matching for switch with Examples
Introduction
Until Java 21, a
OUTPUT of the above code
Each
Guards with when, and handling null
A case can add a condition with
OUTPUT of the above code
Cases are tried from top to bottom, so the guarded
OUTPUT of the above code
Leaving out the
OUTPUT on compiling the above code
This is the real benefit of combining sealed types with switch: when a new subtype is added, the compiler finds every
Dominance
A case label dominates a later one if every value matching the later label would already match the earlier one - for example
OUTPUT on compiling the above code
Java 21 also allows qualified enum constants in case labels, such as
switch could only choose between constant values of a few types - integers, strings and enums. To branch on the type of an object, you wrote a chain of if (obj instanceof ...) tests. Pattern matching for switch, previewed four times from Java 17 to 20 and final in Java 21Click to read tutorial: Overview of New Features in Java 21 (JEP 441), lets switch work with any type, with patterns in its case labels. It builds on switch expressionsClick to read: switch expressions (Java 14) and pattern matching for instanceofClick to read: pattern matching for instanceof (Java 16). This tutorial explains pattern matching for switch with examples - type patterns, when guards, handling null, exhaustiveness, and the dominance rules the compiler enforces.
Type patterns in case labels
Replacing an if-instanceof chain with switch
//SwitchPatternsDemo.java
import java.util.List;
public class SwitchPatternsDemo {
static String formatOld(Object obj) { // before Java 21
if (obj instanceof Integer i) return "int " + i;
else if (obj instanceof Long l) return "long " + l;
else if (obj instanceof String s) return "String \"" + s + "\"";
else return "other " + obj;
}
static String format(Object obj) { // Java 21
return switch (obj) {
case Integer i -> "int " + i;
case Long l -> "long " + l;
case String s -> "String \"" + s + "\"";
case int[] arr -> "int array of length " + arr.length;
default -> "other " + obj;
};
}
public static void main(String[] args) {
System.out.println(formatOld(42));
for (Object o : List.of(42, 7L, "Java", new int[] {1, 2}, 3.14)) {
System.out.println(format(o));
}
}
}
int 42 int 42 long 7 String "Java" int array of length 2 other 3.14
case label is a type pattern, exactly as in instanceof: if the object has that type, it is bound to the variable and the case runs. The switch is clearer than the if chain, and the compiler can check it, as we will see.
when, called a guard. And while a traditional switch throws a NullPointerException when the selector is null, a switch can now handle it with case null -
Guarded patterns and case null
//GuardsAndNull.java
public class GuardsAndNull {
static String classify(Object obj) {
return switch (obj) {
case null -> "nothing";
case String s when s.isBlank() -> "a blank string";
case String s when s.length() > 10 -> "a long string";
case String s -> "a string: " + s;
case Integer i when i < 0 -> "a negative number";
case Integer i -> "a number: " + i;
default -> "something else";
};
}
public static void main(String[] args) {
for (Object o : new Object[] {null, " ", "pattern matching", "Java", -5, 21, 2.5}) {
System.out.println(classify(o));
}
}
}
nothing a blank string a long string a string: Java a negative number a number: 21 something else
String cases come before the unguarded one. case null can also be combined with default as case null, default -> .... Without a case null, a null selector still throws a NullPointerException, as before.
Exhaustiveness with sealed types and enums
A switch with patterns must be exhaustive: every possible value must be handled. Over Object that requires a default. But over a sealedClick to read: sealed type, covering every permitted subtype is enough - and leaving one out is a compile error -
An exhaustive switch over a sealed interface
//ExhaustiveSwitch.java
public class ExhaustiveSwitch {
sealed interface Shape permits Circle, Square, Rect { }
record Circle(double r) implements Shape { }
record Square(double side) implements Shape { }
record Rect(double w, double h) implements Shape { }
static double area(Shape s) {
return switch (s) {
case Circle c -> Math.PI * c.r() * c.r();
case Square sq -> sq.side() * sq.side();
case Rect(double w, double h) -> w * h; // a record pattern works too
}; // no default: all permitted types covered
}
public static void main(String[] args) {
for (Shape s : new Shape[] {new Circle(1), new Square(2), new Rect(2, 3)}) {
System.out.printf("%s -> %.2f%n", s, area(s));
}
}
}
Circle[r=1.0] -> 3.14 Square[side=2.0] -> 4.00 Rect[w=2.0, h=3.0] -> 6.00
Rect case is reported by the compiler -
A switch which misses a permitted type
//NotExhaustive.java
public class NotExhaustive {
sealed interface Shape permits Circle, Square { }
record Circle(double r) implements Shape { }
record Square(double side) implements Shape { }
static String name(Shape s) {
return switch (s) {
case Circle c -> "circle";
};
}
}
NotExhaustive.java:8: error: the switch expression does not cover all possible input values
return switch (s) {
^
1 error
switch which must handle it. If a class is added to a hierarchy without recompiling such a switch, the JVM throws a MatchException at runtime rather than silently doing nothing.
case CharSequence cs before case String s, or an unguarded pattern before a guarded pattern of the same type. The later case could never run, so the compiler reports an error -
A case label dominated by an earlier one
//Dominance.java
public class Dominance {
static String check(Object obj) {
return switch (obj) {
case CharSequence cs -> "char sequence";
case String s -> "string"; // never reached: every String is a CharSequence
default -> "other";
};
}
}
Dominance.java:6: error: this case label is dominated by a preceding case label
case String s -> "string"; // never reached: every String is a CharSequence
^
1 error
case Color.RED, so that a switch over a sealed interface can mix enum constants and record patterns.
Summary
In this tutorial we saw how pattern matching for switch, final in Java 21, lets switch branch on the type of any object. We replaced an if-instanceof chain with type patterns in case labels, refined cases with when guards, and handled null with case null. We then wrote an exhaustive switch over a sealed interface with no default, and saw the compiler errors for a missing case and for a dominated case. Next, read about sequenced collectionsClick to read tutorial: Java 21 Sequenced Collections Explained.Continue Reading Java 18 to 21 Tutorials
Overview of New Features in Java 21Click to read tutorial: Overview of New Features in Java 21 Java 21 Virtual Threads Tutorial with ExamplesClick to read tutorial: Java 21 Virtual Threads Tutorial with Examples Java 21 Record Patterns with ExamplesClick to read tutorial: Java 21 Record Patterns with Examples Java 21 Sequenced Collections ExplainedClick to read tutorial: Java 21 Sequenced Collections Explained Java 21 Generational ZGC ExplainedClick to read tutorial: Java 21 Generational ZGC Explained Java 21 API Additions: StringBuilder.repeat, Math.clampClick to read tutorial: Java 21 API Additions: StringBuilder.repeat, Math.clamp Overview of New Features in Java 18Click to read tutorial: Overview of New Features in Java 18 Overview of New Features in Java 19Click to read tutorial: Overview of New Features in Java 19 Overview of New Features in Java 20Click to read tutorial: Overview of New Features in Java 20
Overview of New Features in Java 21Click to read tutorial: Overview of New Features in Java 21 Java 21 Virtual Threads Tutorial with ExamplesClick to read tutorial: Java 21 Virtual Threads Tutorial with Examples Java 21 Record Patterns with ExamplesClick to read tutorial: Java 21 Record Patterns with Examples Java 21 Sequenced Collections ExplainedClick to read tutorial: Java 21 Sequenced Collections Explained Java 21 Generational ZGC ExplainedClick to read tutorial: Java 21 Generational ZGC Explained Java 21 API Additions: StringBuilder.repeat, Math.clampClick to read tutorial: Java 21 API Additions: StringBuilder.repeat, Math.clamp Overview of New Features in Java 18Click to read tutorial: Overview of New Features in Java 18 Overview of New Features in Java 19Click to read tutorial: Overview of New Features in Java 19 Overview of New Features in Java 20Click to read tutorial: Overview of New Features in Java 20