Pattern Matching for switch - Sip of Java

After several preview rounds, pattern matching for switch made it into JDK 21 as a final feature! Adding pattern matching to switch will provide developers with many quality-of-life improvements when using switch. Let’s take a look!

Pattern Matching

Pattern matching was initially added in Java 16 with pattern matching for instanceof. Pattern matching for instanceof did significantly improve code readability and maintainability, but still left something to be desired when comparing multiple types, like in this example:

interface Shape{
    double area();
}
record Circle(double diameter) implements Shape{...};
record Square(double side) implements Shape{...};
Shape shape = new Circle(10); 
String message; 
if(shape instanceof Circle c) {
   message = "A circle with area of " + c.area();
} else if (shape instanceof Square s) {
   message = "A square with area of " + s.area();
} else {
   message = "Unknown shape";
}

With Java 21, switch has been extended to allow case labels to also be patterns, simplifying the previous example to this:

interface Shape{
    double area();
}
record Circle(double diameter) implements Shape{...};
record Square(double side) implements Shape{...};
Shape shape = new Circle(10); 
String message = switch(shape){
    case Circle c -> "A circle with area of " + c.area();
    case Square s -> "A square with area of " + s.area();
    default -> "Unknown shape";
};

Guard Conditions

With the update for pattern matching, switch can support further refinement with case labels using guard conditions. This can help improve the readability and maintainability of the code by keeping the check conditions within the case label instead of using if statements within the block of a case; this example demonstrates how to use a guard condition:

interface Shape{
    double area();
}
record Circle(double diameter) implements Shape{...};
record Square(double side) implements Shape{...};
Shape shape = new Circle(10);
String message = switch(shape){
   case Circle c when c.area() > 100 
	-> "A large circle with area of " + c.area(); 
   case Circle c -> "A circle with area of " + c.area();
   case Square s -> "A square with area of " + s.area();
   default -> "Unknown shape";
};

Handling null

null is also supported as a case label with the updates to switch. This helps with readability and maintainability as all the logic concerning the switch can be covered in the body of the switch instead of requiring a null check before:

interface Shape{
    double area();
}
record Circle(double diameter) implements Shape{...};
record Square(double side) implements Shape{...};
Shape shape = new Circle(10); 
String message = switch(shape){
    case Circle c -> "A circle with area of " + c.area();
    case Square s -> "A square with area of " + s.area();
    case null -> "Null value";
    default -> "Unknown shape";
};

Exhaustiveness

When using pattern matching in switch, the switch must be exhaustive, even if it’s written as a statement like in this example:

interface Shape{
   double area();
}
record Circle(double diameter) implements Shape{...};
record Square(double side) implements Shape{...};
Shape shape = new Circle(10);
switch(shape){
  case Circle c -> System.out.println("A circle with area of " + c.area());
  case Square s -> System.out.println("A square with area of " + s.area());
  default -> System.out.println("Unknown shape");
};

Sealed Hierachies

When using a sealed hierarchy in a switch, the compiler can read the hierarchy to test for exhaustiveness. In this example, the default case is unnecessary as all paths are covered by the switch. This can help protect against bugs as this will cause a compiler error on this code if a new class is added to the sealed hierarchy in the future.

sealed interface Shape{
    double area();
}
record Circle(double diameter) implements Shape{...};
record Square(double side) implements Shape{...};
Shape shape = new Circle(10);
String message = switch(shape){
   case Circle c -> "A circle with area of " + c.area();
   case Square s -> "A square with area of " + s.area();   
   //No default needed; compiler can read sealed hierarchy
};

Record patterns

Record patterns also became a final feature in Java 21 and are supported in switch:

record Name(String fName, String lName, String mName) {};
Name host = new Name("William","Korando",37);
String printName = switch(host) {
	case Name(var fName, var lName, var mName) -> lName + ", " + fName + " " + mName;
};

Additional Reading

Pattern Matching for switch - JEP 441

Record Patterns - JEP 440

Pattern Matching for instanceof - JEP 394

Happy coding!