Record Patterns

You can use a record pattern to test whether a value is an instance of a record class type (see Record Classes) and, if it is, to recursively perform pattern matching on its component values.

For background information about record patterns, see JEP 440.

The following example tests whether obj is an instance of the Point record with the record pattern Point(double x, double y):

record Point(double x, double y) {}

    static void printAngleFromXAxis(Object obj) {
        if (obj instanceof Point(double x, double y)) {
            System.out.println(Math.toDegrees(Math.atan2(y, x)));
        }
    }     

In addition, this example extracts the x and y values from obj directly, automatically calling the Point record's accessor methods.

A record pattern consists of a type and a (possibly empty) record pattern list. In this example, the type is Point and the pattern list is (double x, double y).

Note:

The null value does not match any record pattern.

The following example is the same as the previous one except it uses a type pattern instead of a record pattern:

    static void printAngleFromXAxisTypePattern(Object obj) {
        if (obj instanceof Point p) {
            System.out.println(Math.toDegrees(Math.atan2(p.y(), p.x())));
        }
    }   

Generic Records

If a record class is generic, then you can explicitly specify the type arguments in a record pattern. For example:

record Box<T>(T t) { }

    static void printBoxContents(Box<String> bo) {
        if (bo instanceof Box<String>(String s)) {
            System.out.println("Box contains: " + s);
        }
    }

You can test whether a value is an instance of a parameterized record type provided that the value could be cast to the record type in the pattern without requiring an unchecked conversion. The following example doesn't compile:

    static void uncheckedConversion(Box bo) {
        // error: Box cannot be safely cast to Box<String>
        if (bo instanceof Box<String>(var s)) {
            System.out.println("String " + s);
        }
    }    

Type Inference

You can use var in the record pattern's component list. In the following example, the compiler infers that the pattern variables x and y are of type double:

    static void printAngleFromXAxis(Object obj) {
        if (obj instanceof Point(var x, var y)) {
            System.out.println(Math.toDegrees(Math.atan2(y, x)));
        }
    }     

The compiler can infer the type of the type arguments for record patterns in all constructs that accept patterns: switch statements, switch expressions, and instanceof expressions.

The following example is equivalent to printBoxContents. The compiler infers its type argument and pattern variable: Box(var s) is inferred as Box<String>(String s)

    static void printBoxContentsAgain(Box<String> bo) {
        if (bo instanceof Box(var s)) {
            System.out.println("Box contains: " + s);
        }
    } 

Nested Record Patterns

You can nest a record pattern inside another record pattern:

    enum Color { RED, GREEN, BLUE }
    record ColoredPoint(Point p, Color c) {}
    record ColoredRectangle(ColoredPoint upperLeft, ColoredPoint lowerRight) {}   

    static void printXCoordOfUpperLeftPointWithPatterns(ColoredRectangle r) {
        if (r instanceof ColoredRectangle(
            ColoredPoint(Point(var x, var y), var upperLeftColor),
                         var lowerRightCorner)) {
            System.out.println("Upper-left corner: " + x);
        }
    }   

You can do the same for parameterized records. The compiler infers the types of the record pattern's type arguments and pattern variables. In the following example, the compiler infers Box(Box(var s)) as Box<Box<String>>(Box(String s)).

    static void nestedBox(Box<Box<String>> bo) {
        // Box(Box(var s)) is inferred to be Box<Box<String>>(Box(var s))
        if (bo instanceof Box(Box(var s))) {    
            System.out.println("String " + s);
        }
    }