A record (Java 16+) is a concise, immutable data carrier: declare the components and the compiler writes the constructor, accessors, equals, hashCode and toString.
What you get for free
import java.util.*;
public class Main {
record Point(int x, int y) {}
public static void main(String[] args) {
Point p = new Point(3, 4);
System.out.println(p); // generated toString
System.out.println(p.x() + p.y()); // accessors: x(), not getX()
System.out.println(p.equals(new Point(3, 4)));
System.out.println(new HashSet<>(List.of(p)).contains(new Point(3, 4)));
}
}Point[x=3, y=4]
7
true
true| Generated member | For record Point(int x, int y) |
|---|---|
| Fields | private final int x; private final int y; |
| Canonical constructor | Point(int x, int y) |
| Accessors | int x(), int y() |
equals / hashCode | Compare / hash all components |
toString | Point[x=3, y=4] |
Compact constructor: validate and normalise
public class Main {
record Range(int lo, int hi) {
Range { // compact canonical constructor: no parameter list
if (lo > hi) throw new IllegalArgumentException("lo > hi: " + lo + " > " + hi);
}
int length() { return hi - lo; } // extra methods are fine
static Range of(int a, int b) { // static factory
return new Range(Math.min(a, b), Math.max(a, b));
}
}
record Email(String value) {
Email {
value = value.trim().toLowerCase(); // reassign the parameter, not this.value
}
}
public static void main(String[] args) {
System.out.println(new Range(2, 9).length());
System.out.println(Range.of(9, 2));
System.out.println(new Email(" Asha@Mail.COM "));
try {
new Range(5, 1);
} catch (IllegalArgumentException e) {
System.out.println("rejected: " + e.getMessage());
}
}
}7
Range[lo=2, hi=9]
Email[value=asha@mail.com]
rejected: lo > hi: 5 > 1Records and shallow immutability
import java.util.*;
public class Main {
record Team(String name, List<String> members) {
Team {
members = List.copyOf(members); // defensive, unmodifiable copy
}
}
public static void main(String[] args) {
List<String> list = new ArrayList<>(List.of("Asha"));
Team t = new Team("core", list);
list.add("Ravi"); // doesn't leak into the record
System.out.println(t.members());
try {
t.members().add("Mei");
} catch (UnsupportedOperationException e) {
System.out.println("members are read-only");
}
}
}[Asha]
members are read-onlyRules and limitations
| Allowed | Not allowed |
|---|---|
| Static fields and methods, instance methods | Extra instance fields |
| Implementing interfaces | Extending a class (records implicitly extend java.lang.Record) |
Nested records, generic records record Pair<A, B>(A a, B b) | Being extended — records are implicitly final |
Overriding accessors / toString / equals | Mutating components (fields are final) |
| Annotations on components | Non-canonical constructors that don't delegate to this(…) |
Record vs Lombok @Data vs a hand-written class?
Are records immutable?
Can a record have a no-arg constructor?