Records in Java
The lesson "How to Create an Immutable Class in Java" built an immutable Money class by hand: private final fields, a constructor, accessors, equals, hashCode and toString. That's about forty lines for "an amount and a currency". A record (standard since Java 16) says the same thing in one line: record Money(long paise, String currency) {}. The compiler writes everything else, correctly, every time. Records are now the everyday way to model plain data in Java.
One Line, Many Members
import java.util.HashSet;
import java.util.Set;
public class Records {
record Money(long paise, String currency) {
static final Money ZERO_INR = new Money(0, "INR");
Money plus(Money other) {
return new Money(paise + other.paise, currency);
}
String display() {
return currency + " " + paise / 100 + "."
+ String.format("%02d", paise % 100);
}
}
public static void main(String[] args) {
Money ticket = new Money(25000, "INR");
Money same = new Money(25000, "INR");
System.out.println(ticket);
System.out.println(ticket.paise() + " " + ticket.currency());
System.out.println(ticket.equals(same) + " " + (ticket == same));
Set<Money> seen = new HashSet<>();
seen.add(ticket);
System.out.println(seen.contains(same));
System.out.println(ticket.plus(new Money(12050, "INR")).display());
System.out.println(Money.ZERO_INR);
}
}Money[paise=25000, currency=INR]
25000 INR
true false
true
INR 370.50
Money[paise=0, currency=INR]How it works
record Money(long paise, String currency)declares two components. From them, the compiler generates private final fields, a constructor, accessors,equals,hashCodeandtoString.- Accessors are named after the components, without
get:ticket.paise(), notgetPaise(). toStringprintsMoney[paise=25000, currency=INR].equalscompares every component, so two records with the same data are equal (though they're still different objects, so==isfalse).hashCodematches it, which makes records safe inHashSetand asHashMapkeys.- You can add methods (
plus,display) and static fields and methods (ZERO_INR).
What the Compiler Generates
javap -p on record Point(int x, int y) {} shows it all:
public final class Point extends java.lang.Record {
private final int x;
private final int y;
public Point(int, int);
public final java.lang.String toString();
public final int hashCode();
public final boolean equals(java.lang.Object);
public int x();
public int y();
}The Rules
| Rule | What happens if you break it |
|---|---|
A record is implicitly final | It can't be extended |
It implicitly extends java.lang.Record | extends isn't allowed: error: '{' expected |
Components are private final | Assigning one: cannot assign a value to final variable x |
| No extra instance fields | error: field declaration must be static |
| Static fields, static methods and instance methods | Allowed |
| Implementing interfaces | Allowed: record Money(...) implements Comparable<Money> |
| Nested and local records | Allowed, and implicitly static |
You can also write a component's accessor yourself (for example, to return a copy), as long as it keeps the same name and return type.
When to Use a Record
| Good fit | Poor fit |
|---|---|
| Values: money, coordinates, date ranges | Objects whose state changes over time: a bank account balance |
| Data carriers: rows from a query, API requests and responses | Entities that need inheritance |
| Map keys and set elements | Classes that must hide some of their data |
| Returning several values from a method | Classes with complex lifecycles |
Common Mistakes
Calling getX(). Record accessors are x().
Expecting a mutable List component to be protected. Copy it with List.copyOf in a compact constructor.
Using records for objects with identity and changing state. An account whose balance changes is a class, not a record.
Interview Corner
What is a record in Java?
A record, standard since Java 16, is a special kind of class for immutable data. Declaring record Point(int x, int y) {} generates a final class extending java.lang.Record with private final fields, a canonical constructor, accessor methods named after the components, and equals, hashCode and toString based on all components. Records can't extend other classes or declare extra instance fields, but they can implement interfaces and add methods and static members.
Is a record fully immutable?
Only shallowly. Its fields are final, so they can't be reassigned, but if a component refers to a mutable object such as an ArrayList or an array, that object's contents can still change. Copy such components into immutable forms in a compact constructor.
Practice
- Easy: Write
record Student(String name, int marks)and print a student, their name and whether two students with the same data are equal. - Harder: Write
record DateRange(LocalDate start, LocalDate end)with a methodlong days()that returns the number of days between them, and a static factoryofWeek(LocalDate start).
Key Takeaways
Was this tutorial helpful?