Java Records
Use Java records to create compact, immutable data-carrier classes with auto-generated accessors, equals, and hashCode.
A record is a class whose only job is to carry data. You declare the fields once in the header, and the compiler generates the constructor, the accessors, equals, hashCode, and toString for you. What used to take 40 lines of getters and boilerplate is now a single line:
public record Point(int x, int y) {}That's the whole class. new Point(3, 4), p.x(), p.y(), p.equals(other), and p.toString() all work and behave exactly the way you'd expect.
Why records exist
Before records, every "data class" needed a private final field per component, a constructor that assigned them, a getter per field, value-based equals and hashCode, and a toString. Most of that code was mechanical and easy to get subtly wrong (forget a field in equals, return the wrong field from a getter, copy-paste a typo). Records collapse all of it into a header, eliminating both the typing and the bugs.
Components and accessors
The arguments in the header are called components. Each one becomes:
- A
private finalfield with the same name. - An accessor method with the same name (not
getX()— justx()).
Point p = new Point(3, 4);
System.out.println(p.x() + ", " + p.y()); // 3, 4The names match because records are about exposing data plainly. There's no get prefix because there's nothing to hide.
Generated equals, hashCode, toString
Two records are equal iff they're the same record type and every component is equal:
Point a = new Point(3, 4);
Point b = new Point(3, 4);
System.out.println(a.equals(b)); // true
System.out.println(a); // Point[x=3, y=4]hashCode combines all the components, so records work correctly as keys in HashMap and HashSet with no extra effort.
Compact constructor
You can validate or normalize the inputs without rewriting the assignments. The compact constructor takes no parameter list and runs before the implicit field assignments:
public record Range(int low, int high) {
public Range {
if (low > high)
throw new IllegalArgumentException("low > high");
}
}You can also reassign the parameter variables inside the compact constructor — the final values are what the fields get:
public record Name(String first, String last) {
public Name {
first = first.strip();
last = last.strip();
}
}Adding methods
Records can have any methods you'd normally write — they just can't have additional instance fields (everything backing the record must be a component):
public record Point(int x, int y) {
public double distanceTo(Point other) {
int dx = x - other.x;
int dy = y - other.y;
return Math.sqrt(dx * dx + dy * dy);
}
}Static fields and static methods are fine. Records can also implement interfaces.
What records can't do
- No inheritance. A record implicitly extends
java.lang.Recordand isfinal— you can't extend a record and a record can't extend another class. - No mutable state. All components are
final. If you want mutation, use a normal class. - No instance fields outside components. You can't sneak in an extra
private int cache;.
These restrictions are the point. A record promises "I am just my components, and nothing else." That promise is what makes equals, hashCode, and serialization safe to auto-generate.
When to use one
Records are right when you'd otherwise write a small immutable data class — DTOs, configuration objects, return types that group a few values together, tuples in pattern-matching switches. They're wrong when the type has identity, owns mutable state, or is the root of a hierarchy.
A worked example
What's next
Records lock a class to a fixed set of data. The next chapter introduces sealed classes, which lock a hierarchy to a fixed set of subtypes — the missing piece for modeling closed algebraic-data-type-like families in Java. Continue to Java sealed classes.
Practice
What does the compiler generate for you when you declare `record Point(int x, int y) {}`?