Step 1 of 6 · Reading · ~3 min
Learn
Modern 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.
A note on this course's grader
Records were previewed in Java 14 and finalised in 16. The grader behind
the exercise below compiles with JDK 13, so a record declaration does
not compile there yet:
record Point(int x, int y) {}
// Main.java:2: error: cannot find symbol
// symbol: class record
Nothing above is wrong — it is what you will write on any modern JDK, and it is what to reach for at work. For the exercise on this page you write the equivalent class by hand instead, which is a useful exercise in its own right: it shows exactly how much boilerplate one record header replaces.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…