🚀 Modern Java 17 → 25 · Advanced

Record patterns in Java

Deconstructing records: case Point(int x, int y).

🧩 The mysteryif (o instanceof Point) { Point p = (Point) o; int x = p.x(); int y = p.y(); ... } Four lines of ceremony just to get two numbers. Record patterns do it in one.

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

✗ Cast and call
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.

✓ Record pattern
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);
}
🔮 Predict it

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));
}
  1. 6
  2. 12
  3. Compile 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.

⚠️ The trap

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.

🤔 Think first

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.

💼 In the real world

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

  1. o instanceof Point(int x, int y) binds x and y
  2. Patterns nest to any depth
  3. var infers component types: Point(var x, var y)
  4. Combine with guards: case Point(var x, var y) when x == y
🤯 Did you know?

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);
    }
}
  1. 12
  2. 7
  3. Point[x=3, y=4]
  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));
    }
}
  1. 7
  2. 13
  3. -7
  4. Compile error
Check your answer

7. Nested patterns pull out all four coordinates at once: (4 − 1) + (6 − 2) = 7.

Next: what if the compiler knew every possible subtype? Sealed hierarchies make a switch exhaustive with no default at all.