A record declares its components in the header, and the compiler automatically generates a canonical constructor, a public accessor method per component (named exactly after the component, not prefixed with "get"), plus correctly-implemented equals, hashCode, and toString — all fields are implicitly final, making a record's instances immutable by design. This targets the extremely common "plain data holder" class, which previously required substantial repetitive boilerplate to implement correctly.
One line replaces dozens
This single line generates everything a traditional immutable data class would need — a constructor, x() and y() accessors (note: no "get" prefix), equals/hashCode comparing by value, and a readable toString.
public record Point(double x, double y) { }
Point p = new Point(3.0, 4.0);
System.out.println(p.x()); // 3.0 — accessor, not getX()
System.out.println(p); // Point[x=3.0, y=4.0] — auto-generated toString
Point p2 = new Point(3.0, 4.0);
System.out.println(p.equals(p2)); // true — compares by VALUE, not referenceRecords can still have methods and validation
A compact canonical constructor lets you add validation logic without repeating the parameter list — assignment to the fields still happens automatically after the compact constructor body runs.
public record Range(int min, int max) {
public Range { // compact constructor — no parameter list repeated
if (min > max) {
throw new IllegalArgumentException("min must be <= max");
}
}
public int length() { // records CAN have additional methods
return max - min;
}
}Records are still classes, with real limitations
A record implicitly extends java.lang.Record, so it cannot extend any other class (though it can implement interfaces); every field is implicitly final; and records are specifically for the immutable-data-holder case, not a general-purpose class replacement — a class needing mutable state or a class hierarchy still needs a regular class.