Record patterns in Java
Deconstructing records: case Point(int x, int y).
Deconstruct in place
A record pattern (final in Java 21) checks the type and extracts the components: o instanceof Point(int x, int y). If it matches, x and y are bound and ready inside the block.
record Point(int x, int y) {}
if (o instanceof Point(int x, int y)) {
draw(x, y); // already extracted
}Old vs new
if (o instanceof Point) {
Point p = (Point) o;
int x = p.x();
int y = p.y();
draw(x, y);
}A type test, a cast and two accessor calls, written by hand.
if (o instanceof Point(int x, int y)) {
draw(x, y);
}The pattern does the test, the cast and the accessor calls in one step.
Patterns nest
Patterns can contain patterns, to any depth. A Line made of two Points can be matched and fully unpacked at once. Use var to let the compiler infer component types, and add guards in a switch: case Point(var x, var y) when x == y ->.
record Line(Point a, Point b) {}
if (o instanceof Line(Point(var x1, var y1),
Point(var x2, var y2))) {
len = Math.hypot(x2 - x1, y2 - y1);
}Your turn
What does this print?
record P(int x, int y) {}
record Box(P lo, P hi) {}
void main() {
var lo = new P(1, 1);
Object o = new Box(lo, new P(4, 3));
if (o instanceof Box(P(var a, var b),
P(var c, var d)))
System.out.println((c - a) * (d - b));
}612Compile error
Show the answer
The nested pattern unpacks all four numbers at once: a=1, b=1, c=4, d=3. (4 − 1) × (3 − 1) = 6.
All components, compatible types
A record pattern must mirror the record header: every component, in declaration order. You can't skip one; use var (or _, coming next) for parts you don't care about. Types must fit too: Point(String x, int y) won't compile, because x is an int and could never match a String.
Where do x and y live?
After if (o instanceof Point(int x, int y)), why can you use x and y inside the if block, but not in an else block?
Think about it, then reveal the answer
Bindings exist only where the pattern is certain to have matched. Inside the if block, it matched, so x and y are in scope. In the else block it didn't, so there's nothing to bind.
Unpacking messages
Payment systems, compilers and event processors match nested data all day: case Transfer(Account(var from), Account(var to), var amount) ->. One line replaces a pyramid of casts and getters, and adding a guard handles special cases right in the label.
Key takeaways
- o instanceof Point(int x, int y) binds x and y
- Patterns nest to any depth
- var infers component types: Point(var x, var y)
- Combine with guards: case Point(var x, var y) when x == y
Record patterns extract components by calling the record's accessor methods, so a custom accessor (say, one returning a defensive copy) runs during matching.
Practice questions
What does this print?
record Point(int x, int y) {}
void main() {
Object o = new Point(3, 4);
if (o instanceof Point(int x, int y)) {
System.out.println(x * y);
}
}- 12
- 7
- Point[x=3, y=4]
- Compile error
Check your answer
12. The pattern checks that o is a Point and binds its components to x and y, so x * y is 12.
What does this print?
record P(int x, int y) {}
record Line(P a, P b) {}
void main() {
Object o = new Line(new P(1, 2), new P(4, 6));
if (o instanceof Line(P(var x1, var y1),
P(var x2, var y2))) {
System.out.println((x2 - x1) + (y2 - y1));
}
}- 7
- 13
- -7
- Compile error
Check your answer
7. Nested patterns pull out all four coordinates at once: (4 − 1) + (6 − 2) = 7.