Skip to content
Records
step 1/6

Reading — step 1 of 6

Learn

~2 min readModern Java

Records (Java 14+) are concise, immutable data carriers. They auto-generate the constructor, accessors, equals, hashCode, and toString — replacing 30+ lines of boilerplate with one declaration.

The basic form

record Point(int x, int y) {}

Point p = new Point(3, 4);
p.x();              // 3 — auto-generated accessor (note the parens)
p.y();              // 4
p.toString();        // Point[x=3, y=4]
p.equals(new Point(3, 4));   // true (structural equality)

The header Point(int x, int y) is both the field declaration AND the constructor signature.

What records auto-generate

For record Point(int x, int y) {}, Java synthesizes:

final class Point {
    private final int x;
    private final int y;
    public Point(int x, int y) { this.x = x; this.y = y; }
    public int x() { return x; }
    public int y() { return y; }
    public boolean equals(Object o) { /* compares all components */ }
    public int hashCode() { /* hashes all components */ }
    public String toString() { /* Point[x=3, y=4] */ }
}

All fields are private final — records are immutable. There's no setter.

Adding methods

Records can have methods:

record Point(int x, int y) {
    public double distance(Point other) {
        int dx = x - other.x;
        int dy = y - other.y;
        return Math.sqrt(dx * dx + dy * dy);
    }
    
    public Point translate(int dx, int dy) {
        return new Point(x + dx, y + dy);   // returns a NEW Point
    }
}

Validation in compact constructor

Records support a compact constructor for validation:

record Email(String address) {
    public Email {
        if (address == null || !address.contains("@")) {
            throw new IllegalArgumentException("invalid email");
        }
    }
}

The parameters are implicit. Field assignment happens AFTER the body. Use this for sanitization or invariant checks.

When to use records

  • DTOs (data transfer objects)
  • Multiple return values: record Result<T>(T value, String error) {}
  • API response shapes
  • Domain models that are pure data with derived methods

When NOT to use records

  • Mutable state — records are immutable by design
  • Inheritance hierarchies — records are implicitly final
  • Frameworks that need a no-args constructor (older Hibernate, etc.)

vs. classes

// Old way (35+ lines):
public class Point {
    private final int x;
    private final int y;
    public Point(int x, int y) { this.x = x; this.y = y; }
    public int getX() { return x; }
    public int getY() { return y; }
    @Override public boolean equals(Object o) { ... }
    @Override public int hashCode() { ... }
    @Override public String toString() { ... }
}

// Record (one line):
record Point(int x, int y) {}

For immutable data, records are the modern default.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…