Record patterns
A record pattern not only checks that an object is a record of the right type but also takes it apart into its components right away. Instead of calling point.x() and point.y() you get ready-made x and y variables. Record patterns arrived in Java 21 and work in instanceof and switch.
Taking a record apart
After the record name you list patterns for its components in parentheses — in the same order as in the record declaration:
record Point(int x, int y) {}
void main() {
Object value = new Point(3, 4);
if (value instanceof Point(int x, int y)) {
IO.println("x = " + x + ", y = " + y);
}
}Output:
x = 3, y = 4The variable names in the pattern can differ from the component names: only the order and the types matter.
In switch
Record patterns are especially expressive in a switch. Each branch gets the data of its shape right away:
sealed interface Shape permits Circle, Rectangle {}
record Circle(double radius) implements Shape {}
record Rectangle(double width, double height) implements Shape {}
String describe(Shape shape) {
return switch (shape) {
case Circle(double r) -> "a circle with radius " + r;
case Rectangle(double w, double h) when w == h -> "a square with side " + w;
case Rectangle(double w, double h) -> "a rectangle " + w + " by " + h;
};
}
void main() {
IO.println(describe(new Circle(2)));
IO.println(describe(new Rectangle(3, 3)));
IO.println(describe(new Rectangle(2, 5)));
}Output:
a circle with radius 2.0
a square with side 3.0
a rectangle 2.0 by 5.0The when guard works here too, and it can use the variables obtained from taking the record apart.
This combination — a sealed interface, records and a switch with patterns — is called data-oriented programming. The data is described by simple immutable records, and the operations on it are gathered in one switch. If you add a new kind of shape, the compiler itself points to every switch that needs a new branch.
var in a pattern
To avoid repeating the component types, you can write var instead — the compiler infers the type itself:
record Order(String customer, double amount) {}
void main() {
Object value = new Order("Anna", 49.90);
if (value instanceof Order(var customer, var amount)) {
IO.println(customer + " paid €" + amount);
}
}Output:
Anna paid €49.9Nested patterns
If a component of a record is itself a record, patterns can be nested to take the object apart to any depth:
record Point(int x, int y) {}
record Line(Point start, Point end) {}
void main() {
Object value = new Line(new Point(0, 0), new Point(3, 4));
if (value instanceof Line(Point(var x1, var y1), Point(var x2, var y2))) {
double length = Math.hypot(x2 - x1, y2 - y1);
IO.println("Line length: " + length);
}
}Output:
Line length: 5.0Without patterns you'd have to write line.end().x() - line.start().x() and so on.
The unnamed variable
If you don't need some component, write _ in its place — the unnamed variable (Java 22). It shows that the value is skipped on purpose:
record Order(String customer, double amount, String city) {}
void main() {
List<Order> orders = List.of(
new Order("Anna", 20.0, "Rome"),
new Order("Oleg", 35.5, "Oslo"));
for (Order order : orders) {
if (order instanceof Order(var name, _, var city)) {
IO.println(name + " from " + city);
}
}
}Output:
Anna from Rome
Oleg from OsloCommon mistake
A pattern must list all components of the record. Leave out even one and the program won't compile:
record Point(int x, int y) {}
void main() {
Object value = new Point(3, 4);
if (value instanceof Point(int x)) {
IO.println(x);
}
}Output:
Main.java:6: error: incorrect number of nested patterns
if (value instanceof Point(int x)) {
^
required: int,int
found: int
1 error
error: compilation failedUnneeded components aren't left out but replaced with _: Point(int x, _).