Record Patterns & Deconstruction

Reviewed & published by Brayan K

A record pattern matches a record and pulls its components straight out into named variables in one step — and because patterns nest, it can take an entire data structure apart inside a single instanceof or switch.

Part of the free Java course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

Before You Start

You should know records, pattern matching, and sealed classes. Record patterns are standard from Java 21.

What You'll Learn

The Big Idea — Unpack the Box at the Door

💡 Analogy: A type pattern hands you a sealed box and says "this is a Point" — you still have to open it (p.x(), p.y()) to use what's inside. A record pattern unpacks the box at the door: Point(int x, int y) sets out the contents — x and y — ready to use. And if a box contains smaller boxes, nested patterns unpack those too, in one motion.

The result reads like the data's own shape, top to bottom, instead of a string of accessor calls.

1️⃣ Deconstructing in instanceof

Write the record's name followed by a pattern for each component: obj instanceof Point(int x, int y). If obj is a Point, you immediately get x and y — no accessor calls, no cast. Each component sub-pattern can be an explicit type or var.

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

    public static void main(String[] args) {
        Object obj = new Point(3, 4);

        // Record pattern: match Point AND deconstruct it into x and y.
        if (obj instanceof Point(int x, int y)) {
            System.out.println("x=" + x + ", y=" + y);
            System.out.println("distance^2 from origin = " + (x * x + y * y));
        }

        // Without record patterns you'd bind the whole Point, then call
        // accessors p.x() and p.y() yourself.
    }
}

2️⃣ Nested Record Patterns

Because a record's components can be records, patterns nest. A Line made of two Points can be torn down to its four numbers in a single pattern. Notice how the pattern's shape mirrors the data's shape exactly.

Line(Point(var x1, var y1), Point(var x2, var y2))
 ^outer record  ^inner record   ^inner record
                bind x1,y1      bind x2,y2
public class Main {
    record Point(int x, int y) {}
    record Line(Point start, Point end) {}

    public static void main(String[] args) {
        Object shape = new Line(new Point(0, 0), new Point(3, 4));

        // Nested record patterns deconstruct all the way down in one go.
        if (shape instanceof Line(Point(var x1, var y1), Point(var x2, var y2))) {
            int dx = x2 - x1, dy = y2 - y1;
            double len = Math.sqrt(dx * dx + dy * dy);
            System.out.println("from (" + x1 + "," + y1 + ") to (" + x2 + "," + y2 + ")");
            System.out.println("length = " + len);
        }
    }
}

3️⃣ Record Patterns + Sealed = Exhaustive switch

This is the modern Java sweet spot. A sealed interface of record subtypes, processed by a switch whose arms are record patterns: each arm deconstructs its case, and the compiler proves you've covered them all — so no default.

public class Main {
    sealed interface Shape permits Circle, Rect {}
    record Circle(double r) implements Shape {}
    record Rect(double w, double h) implements Shape {}

    static String describe(Shape s) {
        // Record patterns inside a switch over a SEALED type:
        // deconstruct and stay exhaustive (no default).
        return switch (s) {
            case Circle(double r)      -> "circle r=" + r + " area=" + (Math.PI * r * r);
            case Rect(double w, double h) -> "rect " + w + "x" + h + " area=" + (w * h);
        };
    }

    public static void main(String[] args) {
        System.out.println(describe(new Circle(2)));
        System.out.println(describe(new Rect(3, 4)));
    }
}

4️⃣ Combining with Guards and Types

Component sub-patterns can be specific types, and the whole pattern can carry a when guard. Order matters: more specific patterns and guarded arms come first. Below, two Strings, two equal ints, two ints, and a mixed pair are each handled distinctly.

public class Main {
    record Pair(Object first, Object second) {}

    static String classify(Object o) {
        // Record patterns combine with guards (when) and type patterns.
        return switch (o) {
            case Pair(String a, String b)   -> "two strings: " + a + ", " + b;
            case Pair(Integer a, Integer b) when a.equals(b) -> "equal ints: " + a;
            case Pair(Integer a, Integer b) -> "two ints: " + (a + b);
            case Pair(var a, var b)         -> "mixed: " + a + " & " + b;
            default                         -> "not a pair";
        };
    }

    public static void main(String[] args) {
        System.out.println(classify(new Pair("hi", "there")));
        System.out.println(classify(new Pair(5, 5)));
        System.out.println(classify(new Pair(2, 3)));
        System.out.println(classify(new Pair("x", 9)));
        System.out.println(classify("not a pair"));
    }
}

🧠 Quick Recall

Predict the result before opening each answer.

record P(int a, int b) {}
Object o = new P(7, 2);
if (o instanceof P(var a, var b))
    System.out.println(a - b);

Answer: 5. The pattern matches and binds a=7, b=2, so a - b is 5.

record P(int a, int b) {}
if (o instanceof P(var a)) { }

Answer: No. A record pattern must bind every component, so a 2-component record needs two sub-patterns. error: record pattern P does not match. Use P(var a, var b).

case Pair(Integer a, Integer b) when a.equals(b) -> "eq";
case Pair(Integer a, Integer b) -> "ne";

Answer: "eq". The guarded arm comes first and 4 equals 4, so it wins before the general two-ints arm is considered.

🎯 Your Turn — Deconstruct a Name

Use a record pattern in instanceof to pull out first and last and build a greeting.

public class Main {
    record Name(String first, String last) {}

    static String greet(Object o) {
        // YOUR TURN: use a record pattern in instanceof to pull out first and
        // last, then return "Hello, FIRST LAST!". For anything else, return
        // "Hello, stranger!".
        //
        // Expected:
        //   Hello, Ada Lovelace!
        //   Hello, stranger!
        return "TODO";
    }

    public static void main(String[] args) {
        System.out.println(greet(new Name("Ada", "Lovelace")));
        System.out.println(greet("???"));
    }
}

🧩 Mini-Challenge — Sum a binary tree

Combine sealed records and nested record patterns to total a tree of integers recursively.

public class Main {
    // MINI-CHALLENGE: sum a binary tree of ints using sealed + record patterns.
    //
    // sealed interface Tree permits Leaf, Node {}
    // record Leaf(int value) implements Tree {}
    // record Node(Tree left, Tree right) implements Tree {}
    //
    // Write sum(Tree) with an EXHAUSTIVE switch that deconstructs:
    //   case Leaf(int v)         -> v
    //   case Node(Tree l, Tree r) -> sum(l) + sum(r)
    //
    // Expected output:
    //   sum = 6

    public static void main(String[] args) {
        // Tree t = new Node(new Node(new Leaf(1), new Leaf(2)), new Leaf(3));
        // System.out.println("sum = " + sum(t));
    }
}

Common Errors

Pro Tips

📋 Quick Reference

ConceptSyntaxNote
instanceof deconstructo instanceof Point(int x, int y)Binds x, y
switch deconstructcase Point(int x, int y) -> ...No cast, no accessors
var componentsPoint(var x, var y)Types inferred
Nested patternLine(Point(var x, var y), ...)Deep deconstruction
Typed componentPair(Integer a, Integer b)Constrains the type
Guardedcase P(var a, var b) when ...Adds a boolean test
All components requiredone sub-pattern eachArity must match
Over sealed typesarm per record subtypeExhaustive, no default
Nullcase null -> ...Pattern won't match null
SinceJava 21JEP 440

📚 Resources / Keep Learning

🎉 Lesson Complete!

You can now deconstruct records inline with record patterns, nest them to take apart whole structures, use var for components, add when guards, and combine sealed types, records, and patterns into exhaustive switches.

Practice quiz

What does a record pattern like Point(int x, int y) do?

  • Creates a new Point
  • Calls a constructor
  • Matches a Point and binds its components to x and y
  • Compares two points

Answer: Matches a Point and binds its components to x and y. A record pattern matches the type and deconstructs it, binding each component to a variable in one step.

In which Java version did record patterns become a standard feature?

  • Java 21
  • Java 16
  • Java 17
  • Java 11

Answer: Java 21. Record patterns were finalized in Java 21 (JEP 440).

What is a nested record pattern?

  • A record inside a method
  • Two records in a switch
  • A recursive method
  • A pattern that deconstructs a record whose components are themselves records

Answer: A pattern that deconstructs a record whose components are themselves records. Line(Point(var x1, var y1), Point(var x2, var y2)) deconstructs nested records all the way down.

Can you use var for component bindings in a record pattern?

  • No
  • Yes — Point(var x, var y) infers each component type
  • Only for the outer record
  • Only in instanceof

Answer: Yes — Point(var x, var y) infers each component type. var is allowed for component bindings; the compiler infers each component's type.

Why do record patterns pair so well with sealed types?

  • A switch can deconstruct and stay exhaustive with no default
  • They run faster
  • They reduce memory
  • They allow inheritance

Answer: A switch can deconstruct and stay exhaustive with no default. Over a sealed hierarchy of records, a switch can deconstruct each case and the compiler proves exhaustiveness.

Without record patterns, how would you read a matched record's data?

  • You couldn't
  • Use reflection
  • Bind the whole record, then call its accessor methods
  • Cast to Object

Answer: Bind the whole record, then call its accessor methods. Previously you bound the record (case Point p) and called p.x(), p.y() yourself.

Can a record pattern include a guard with when?

  • No
  • Yes — case Pair(Integer a, Integer b) when a.equals(b) -> ...
  • Only in instanceof
  • Only without nesting

Answer: Yes — case Pair(Integer a, Integer b) when a.equals(b) -> .... Record patterns combine with when guards just like type patterns.

Do the component types in a record pattern have to exactly match the record's declared types?

  • They can be anything
  • They must be Object
  • They must be primitives
  • They must match or be valid sub-patterns

Answer: They must match or be valid sub-patterns. Each sub-pattern must be compatible with the record component's type (the same type, var, or a nested pattern).

What is the main readability benefit of record patterns?

  • Shorter class names
  • Deconstruct data inline instead of repeated accessor calls
  • Automatic null checks
  • Faster compilation

Answer: Deconstruct data inline instead of repeated accessor calls. You name the pieces you need right in the pattern, avoiding a chain of p.a().b() accessor calls.

Combining sealed + records + record patterns in a switch is often called...

  • Reflection
  • Lazy evaluation
  • Data-oriented programming
  • Generics

Answer: Data-oriented programming. Sealed types, records, and patterns together enable a data-oriented programming style.

Continue this course

Frequently asked questions

What exactly is a record pattern?

A record pattern matches a value against a record type and, in the same step, deconstructs it into its components, binding each to a variable. Point(int x, int y) matches when the value is a Point and gives you x and y directly. It is the natural extension of type patterns: a type pattern binds the whole object (case Point p), while a record pattern binds the pieces inside it (case Point(int x, int y)). Record patterns were finalized in Java 21.

How do nested record patterns work?

Because a record's components can themselves be records, you can nest patterns to deconstruct an entire structure at once. Line(Point(var x1, var y1), Point(var x2, var y2)) matches a Line and reaches all the way down to the four coordinates in one pattern. This replaces a tedious chain like line.start().x() with named variables right where you need them, and it reads top-to-bottom like the data's own shape.

Can I use var inside a record pattern?

Yes. You can write the explicit component type — Point(int x, int y) — or let the compiler infer it with var — Point(var x, var y). var is especially convenient in deeply nested patterns where spelling out every type would be noisy. The inferred type is exactly the record component's declared type, just as with local-variable var.

Why are record patterns so powerful with sealed types?

A sealed interface gives you a closed set of record subtypes, and a switch can have one record-pattern arm per subtype. The compiler then proves the switch is exhaustive — no default needed — and each arm deconstructs its record inline. This combination of sealed + records + record patterns is the heart of 'data-oriented programming' in modern Java: you model data as a closed family of records and process it with a single, fully checked switch.

Do record patterns work with guards and null?

Yes. A record pattern can carry a when guard, e.g. case Pair(Integer a, Integer b) when a.equals(b) -> ..., and the bound components are visible to both the guard and the arm body. As for null: a record pattern only matches a non-null value of the record type, so a null selector falls through to a case null (in a switch) or simply doesn't match (in instanceof). Note that the components themselves can still be null and will be bound as such.

Related lessons