Records
Concise immutable data carriers: components, compact constructors, methods and limitations.
A huge share of the classes in real programs just carry data: a Point, a Money amount, a row from a database, the JSON body of a request. Writing them by hand means a constructor, private final fields, getters, equals, hashCode and toString, around 40 lines of boilerplate that is easy to get subtly wrong.
A record (standard since Java 16) is a special kind of class that declares all of that in a single line.
Your first record#
That one line, record Point(int x, int y) { }, gives you:
- a
private finalfield for each component (xandy), - a canonical constructor
Point(int x, int y)that assigns them, - an accessor method per component, named
x()andy()(notgetX()), equalsandhashCodebased on all components,- a readable
toStringlikePoint[x=3, y=4].
It is roughly equivalent to this hand-written class:
Records are immutable#
There are no setters, and the fields are final. To "change" a record, create a new one, usually with a small wither method:
Records are shallowly immutable. If a component is a mutable object, like an
ArrayList, the list itself can still be changed. Store a defensive copy withList.copyOf(...)in the constructor (shown below).
Validation with a compact constructor#
To check or normalise the arguments, write a compact constructor: the record name with no parameter list. Your code runs first, then the fields are assigned automatically from the (possibly reassigned) parameters:
In a compact constructor you must not write this.address = address;; the compiler does that for you (and reports an error if you try).
Adding more to a record#
A record body can contain most things a class can:
Allowed in a record:
- static fields, static methods and static nested types,
- instance methods, including overriding
toString,equals,hashCodeor an accessor, - implementing interfaces,
- extra constructors, as long as they call
this(...)(eventually the canonical constructor).
Limitations#
- No extra instance fields. The state is exactly the components.
private int cache;inside a record is a compile error. - Records are implicitly
finaland cannot be extended. - Records cannot extend a class. Every record already extends
java.lang.Record. (Implementing interfaces is fine.) - Component fields are
final, so there are no setters. - Some frameworks (notably JPA/Hibernate entities) need mutable classes with no-arg constructors, so records don't suit entities. They are perfect for DTOs, API responses, keys and value objects, and Jackson and Spring handle records fine.
Local records and records as keys#
You can declare a record inside a method for a quick temporary type. And because equals/hashCode come for free, records make excellent map keys:
With a normal class and no equals/hashCode, that lookup would return null. (The Maps and equals()/hashCode() lessons explain why.)
Records and pattern matching#
Since Java 21, you can deconstruct a record directly in instanceof and switch, pulling out its components in one step:
The Modern Java lesson covers record patterns with sealed types and switch.
Record or class?#
Common mistakes#
- Calling
getX()on a record; the accessor isx(). - Assigning
this.field = ...inside a compact constructor. - Adding an instance field to a record.
- Assuming a record holding a
Listis deeply immutable; copy it withList.copyOf. - Using records for JPA entities.
What's next#
Records model data with a fixed shape. Enums model a fixed set of values, like days of the week or order statuses, with full type safety.
Check your understanding
Quick quiz
1.For
record Point(int x, int y) {}, how do you read the x value ofp?2.Which of these is NOT allowed in a record?
3.What does a compact constructor
record Age(int value) { Age { if (value < 0) throw new IllegalArgumentException(); } }do?
Finished reading?
Mark this lesson complete to track your progress.