A record class (Java 16, JEP 395) is a class whose state is a fixed list of values, its
components, declared in its header. The compiler writes the fields, the
constructor, the accessors and equals, hashCode and
toString from that list (JLS 8.10).
Declaration
RecordDeclaration:
{ClassModifier} record TypeIdentifier [TypeParameters] RecordHeader [ClassImplements] RecordBody
RecordHeader:
( [RecordComponent {, RecordComponent}] )
RecordComponent:
{RecordComponentModifier} UnannType Identifier
{RecordComponentModifier} UnannType {Annotation} ... Identifier
RecordBody:
{ {ClassBodyDeclaration | CompactConstructorDeclaration} }
record Point(int x, int y) {}
Point p = new Point(3, 4);
p.x(); // 3
p.equals(new Point(3, 4)); // true
p.toString(); // "Point[x=3, y=4]"
What the compiler declares
For record R(T1 c1, T2 c2):
| Member | Form | Behaviour |
|---|---|---|
| Component fields | private final T1 c1; | One per component |
| Accessors | public T1 c1() | Returns the field. Named exactly like the component, with no get prefix |
| Canonical constructor | R(T1 c1, T2 c2) | Assigns each field from its parameter, in order. Same access as the record (package access if the record has none) |
equals | public boolean equals(Object) | True if the argument is an R whose components are all equal: == for primitives (with float and double compared as by Float.compare and Double.compare), Objects.equals for references |
hashCode | public int hashCode() | Combined from the components’ hash codes; the exact formula is not specified |
toString | public String toString() | The record’s name and its components’ names and values; the exact format is not specified |
Any of these except the fields may be written explicitly instead. An explicit
accessor must be public, have the component’s type as its return type,
have no parameters, no type parameters and no throws clause, and not be
static.
Constructors
Compact canonical constructor
A compact constructor is a canonical constructor written without the parameter list. Its body runs first, and the fields are assigned from the parameters at the end. It is meant for checking and normalising the arguments:
record Range(int low, int high) {
Range { // compact canonical constructor
if (low > high) throw new IllegalArgumentException(low + " > " + high);
}
}
record Name(String value) {
Name {
value = value.strip(); // reassigns the parameter, not the field
}
}
In a compact constructor, assigning a component field (this.value =
…) is an error, and so is a return statement.
Explicit canonical constructor
A normal constructor whose parameter list matches the components, in order, by type
and by name, is canonical. It must assign every field, cannot call
this(…) or super(…), cannot have a throws
clause, and must be at least as accessible as the record. A record may not have both a
normal canonical constructor and a compact one.
Other constructors
A record may have any number of other constructors, but each must call another
constructor of the record with this(…), so that every path ends in the
canonical constructor. Since Java 25 statements may come before that call (see
flexible constructor
bodies).
record Point(int x, int y) {
static final Point ORIGIN = new Point(0, 0); // static fields are allowed
Point(int both) { this(both, both); } // delegates to the canonical constructor
Point withX(int nx) { return new Point(nx, y); } // instance methods are allowed
}
Restrictions
- A record is implicitly
final(writing it is allowed) and cannot beabstract,sealedornon-sealed. It cannot have anextendsclause: its superclass is alwaysjava.lang.Record. It may implement interfaces. - The body may not declare instance fields or instance initialisers. It may declare static fields, static initialisers, static and instance methods, nested types and constructors.
- Methods may not be
abstractornative. - A component may not be named
clone,finalize,getClass,hashCode,notify,notifyAll,toStringorwait. - The last component may be variable arity,
record Path(String... parts); its field is an array. - Nested and local records are implicitly
static, so a local record cannot use the enclosing method’s variables. - Records may be generic:
record Pair<A, B>(A first, B second) {}. - The fields are final, but an object they refer to, such as an array or a list, can still change. Copy mutable arguments in a compact constructor to keep a record truly immutable.
Annotations on components
An annotation on a record component is applied to each element derived from it whose
kind the annotation’s @Target allows: the field, the accessor, the
canonical constructor’s parameter, the type of each, and the component itself
(ElementType.RECORD_COMPONENT, for reflection). An annotation that fits
none of these is an error.
Records and patterns
A record’s components can be taken apart with a record pattern (Java 21), which calls the accessors:
if (obj instanceof Point(int x, int y) && x == y) {
System.out.println("on the diagonal at " + x);
}
Java serialisation also treats records specially: a record is read back by calling its canonical constructor, so the constructor’s checks always run.