Java Record Patterns and Pattern Matching

Java record patterns refer to the mechanism of deconstructing record values such that they can be combined with pattern matching enabling more sophisticated data queries.

This tutorial will discuss the record patterns in detail along with how they help in pattern matching for instanceof and switch statements. The record patterns feature (completed) is part of the Java 21 release.

1. Quick Recap of Java Records

Records, introduced in Java 14, act as transparent carriers for immutable data. Conceptually similar to tuples, records are used in places where a class is created only to act as a plain data carrier.

In the following code snippet, the Shape is a record type. In runtime, JVM generates the following:

  • An all-arguments constructor.
  • The getter methods with a pattern instance.x() where x is the component name.
  • The hashCode() and equals() methods.
  • The toString() method.
record Shape(String type, long unit){}

void main() {

  Shape circle = new Shape("Circle", 10);

  System.out.println(circle.type()); // "Circle"
  System.out.println(circle.unit()); // 10
  System.out.println(circle);       //Shape[type=Circle, unit=10]

2. Record Patterns

2.1. What is a Pattern?

In general terms, a pattern is a combination of a predicate (or test) that can be applied to a target and a set of local variables, called pattern variables, that are assigned values extracted from the target only if the test is successful.

In the following example,

  • instanceof operator is the predicate
  • the shape is the target, and
  • s is the pattern variable which is initialized only if the ‘shape instanceof Rectangle’ is true.
Shape shape = ...;

if (shape instanceof Shape s) {
    // we can use s here

2.2. What is a Record Pattern?

When we combine the concept of patterns with record types, it is called record patterns. A record pattern consists of a record class type and a (possibly empty) pattern list which is used to match against the corresponding target record.

The record pattern can also disaggregate an instance of a record into its components. For example, in the following code snippet, the type and unit are pattern variables.

record Shape(String type, long unit){}

void main() {

  Shape circle = new Shape("Circle", 10);

  if (circle instanceof Shape(String type, long unit)) {

    System.out.println("Area of " + type + " is : " + Math.PI * Math.pow(unit, 2));

The program output:

Area of Circle is : 314.1592653589793

When the program is executed, notice how the values of component type and unit are automatically populated

The null value does not match any record pattern.

For reference, if we do not use the record patterns then the above code will be written as follows:

if (circle instanceof Shape shape) {

  System.out.println("Area of " + shape.type() + " is : " + Math.PI * Math.pow(shape.unit(), 2));

Notice how the circle instance is first cast to shape and then we use the accessor method to get the component values. In the above code, the shape is restricted to the scope of instanceof block and does not provide any further value.

Using record patterns helps avoid such unnecessary casts, as well as, record components are directly accessed making the code more concise, clean, and readable.

3. Pattern Matching

Pattern matching is referred to be the operation to take a type pattern (e.g. record pattern) and perform matching against multiple patterns. We can do pattern matching with conditional statements such as if-else, instanceof, switch statements etc.

In the following example, target shape has been tested against multiple patterns in the case statements.

interface Shape {}
record Circle(double radius) implements Shape { }
record Square(double side) implements Shape { }
record Rectangle(double length, double width) implements Shape { }

void main() {

  Shape shape = new Circle(10);

  switch(shape) {

    case Circle c:
      System.out.println("The shape is Circle with area: " + Math.PI * c.radius() * c.radius());

    case Square s:
      System.out.println("The shape is Square with area: " + s.side() * s.side());

    case Rectangle r:
      System.out.println("The shape is Rectangle with area: + " + r.length() * r.width());

      System.out.println("Unknown Shape");

Pattern matching, when combined with record destructuring, enables us to write very powerful pattern expressions that are very clean, concise, and readable.

In the following example, object destructuring extracts the record component values and directly populates them into the pattern variables. This removes the need for temporary pattern variables s, r or c that is only purpose is to provide access to component values. Here we are directly getting the component values and using them.

switch(shape) {

  case Circle(double radius):
    System.out.println("The shape is Circle with area: " + Math.PI * radius * radius);

  case Square(double side):
    System.out.println("The shape is Square with area: " + side * side);

  case Rectangle(double length, double width):
    System.out.println("The shape is Rectangle with area: + " + length * width);

    System.out.println("Unknown Shape");

Clearly, the above code is more concise and readable.

4. Conclusion

This Java tutorial discusses two relatively newer features added to the language i.e. record patterns and pattern matching. The key to understanding these related terms is first understanding what a pattern is. Then we can combine the concept of patterns to record types, and finally go deeper into pattern matching with instanceof and switch expressions.

Happy Learning !!

Source Code on Github


Notify of
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.

Our Blogs

REST API Tutorial

Dark Mode

Dark Mode