3 min read

Record Patterns in Java

Pattern matching specific for record types.
Record Patterns in Java

Record Patterns

Records have special treatment given to them in patterning matching. Record patterns can be used using the instanceof operator and switches. Records can be matched just like any other type, like what is shown in the following:

public record Point(int x, int y) {
}

switch (object) {
    case Point p -> System.out.println(p.x() + p.y());
    default -> System.out.println("default");
}

Record patterns allow you to use the components as values instead of calling a record's accessor methods. The previous example could be written with the following:

public record Point(int x, int y) {
}

switch (object) {
    case Point(int x, int y) -> System.out.println(x + y);
    default -> System.out.println("default");
}

These variables have the scope from when they are declared to the end of the block of code it is matching. Using the instanceof operator, the previous example would look like the following:

public record Point(int x, int y) {
}

if (object instanceof Point(int x, int y)) {
    System.out.println(x + y);
} else {
    System.out.println("default");
}

Nested Record Patterns

If a record contains another record as one of its components, you can specify the components for the nested record as well. This can be done with the following:

public record Color(int value) {
}

public record Point(int x, int y, Color color) {
}

final var point = new Point(1, 3, new Color(0xFFFFFF));

switch (point) {
    case Point(int x, int y, Color(int c)) ->
        System.out.println("Color: " + c + " x: " + x + " y: " + y);
    default -> System.out.println("default");
}

This can be written using the instanceof operator with the following:

if (point instanceof Point(int x, int y, Color(int c))) {
    System.out.println("Color: " + c + " x: " + x + " y: " + y);
} else {
    System.out.println("default");
}

Generic Patterns

When you are working with records that have generics, you can match on those generic types. For example:

public record GenericRecord<T>(T value) {
}


final Object object = new GenericRecord<>(true);

switch (object) {
    case GenericRecord(Number n) -> System.out.println(n);
    case GenericRecord(String s) -> System.out.println(s);
    case GenericRecord(Boolean b) -> System.out.println(b);
    case GenericRecord(Character c) -> System.out.println(c);
    default -> System.out.println("default");
}

In this example, the third label will match since the generic type is Boolean. Using the instanceof operator, the previous example would look like the following:

final Object object = new GenericRecord<>(true);

if (object instanceof GenericRecord(Number n)) {
    System.out.println(n);
} else if (object instanceof GenericRecord(String s)) {
    System.out.println(s);
} else if (object instanceof GenericRecord(Boolean b)) {
    System.out.println(b);
} else if (object instanceof GenericRecord(Character c)) {
    System.out.println(c);
} else {
    System.out.println("default");
}

Guarded Pattern Label (when Clause)

Record patterns can also have a when clause, just like any other type pattern. A when clause allows you to add a boolean expression after the record pattern. This is called a guarded pattern label.

final Object object = new GenericRecord<>(true);

switch (object) {
    case GenericRecord(Number n) when n.longValue() != 0 ->
        System.out.println(n);
    case GenericRecord(String s) when !s.isEmpty() ->
        System.out.println(s);
    default -> System.out.println("default");
}

Type Inference

You can use type inference with record types if you aren't concerned with the type. The types will be inferred regardless of if you are using generics or not.

switch (object) {
    case Point(var x, var y) -> System.out.println("x:" + x + " y:" + y);
    default -> System.out.println("default");
}

This same thing can be written using the instanceof operator with the following:

if (object instanceof Point(var x, var y)) {
    System.out.println("x:" + x + " y:" + y);
} else {
    System.out.println("default");
}

Conclusion

Record patterns have special treatment that type patterns do not have. If you need the components of a record, use record matching. If not, simple type matching is sufficient.