Modern Record
Java records are concise classes for modeling immutable-style data carriers with a fixed set of components. They automatically provide accessors, a canonical constructor, and implementations of `equals`, `hashCode`, and `toString` based on their declared components.
A record is declared with a record header containing its components, such as `record User(long id, String name) {}`. The components define the record's state and determine its generated API. Each component has an accessor named after the component, so `user.name()` is used instead of a conventional `getName()` method.
The compiler supplies a canonical constructor that initializes the record components and implementations of `equals`, `hashCode`, and `toString`. A record is implicitly `final`, so it cannot be subclassed, and a record cannot extend another class. It can, however, implement interfaces.
Records are useful when the main purpose of a type is to represent a small, well-defined aggregate of values. They reduce boilerplate compared with an ordinary class, making DTOs, request/response objects, and value-like data structures easier to read.
A record does not make referenced objects deeply immutable. A component declared as `List<String>` is a final reference, but the list itself may still be mutable unless the constructor or factory method makes an appropriate defensive copy. Records can also contain methods, static members, validation logic, and custom constructors.
Key points
- A record is a compact declaration for a data-oriented class.
- Component accessors use the component name, such as `user.id()`.
- The compiler provides a canonical constructor, `equals`, `hashCode`, and `toString`.
- Records are implicitly `final` and cannot extend another class.
- Record components are final, but referenced mutable objects are not automatically immutable.
- Records can implement interfaces and contain custom methods or validation.
Interview questions
What is a Java record, and why would you use one?
A record is a Java language construct intended for classes whose primary purpose is to carry a fixed set of values. The declared components become the object's state and are exposed through component-named accessors. Java also supplies common methods such as `equals`, `hashCode`, and `toString`. I would use a record when the type is primarily a data carrier and does not need the flexibility of a conventional class.
Are Java records immutable?
Records provide final component fields, so their component references cannot be reassigned after construction. However, this is not deep immutability. If a component refers to a mutable object such as an `ArrayList`, that object can still change unless the record creates or receives an appropriate immutable or defensive copy. Therefore, records are best described as providing shallow immutability of their component fields.
What methods does the compiler provide for a record?
A record gets a canonical constructor corresponding to its declared components. It also gets an accessor for each component, plus implementations of `equals`, `hashCode`, and `toString`. These methods are based on the record components. Developers can explicitly declare or customize relevant behavior when the record's requirements call for it.
Can a record contain validation logic?
Yes. A record can define a compact constructor, which is useful for validating or normalizing incoming component values before assignment. For example, a constructor can reject a negative identifier or require a non-null string. This allows a record to enforce construction-time invariants while retaining its concise declaration.
Can a Java record extend a class or be extended?
No. A record is implicitly final, so another class cannot extend it. A record also cannot extend an arbitrary class because record classes already have `java.lang.Record` as their superclass. A record can implement one or more interfaces, which is the usual way to give it a polymorphic contract.