Many classes exist simply to hold data: a point with an x and a y, a product with a name and a price, a search result with a title and a link. Written as a normal class, each one needs private fields, a constructor, a getter for every field, and usually equals, hashCode and toString too. That can be fifty lines of code that says almost nothing new.
A record, added in Java 16, does all of that in a single line. You list the components, and Java generates the rest. Records are also immutable: once created, their values can't change, which makes them safe to share, use as map keys and pass between parts of a program.
In this lesson you'll declare records, see what Java generates, validate values in a compact constructor, add your own methods, make changed copies, and learn when a regular class is still the better choice.
Declaring a record
record Point(int x, int y) {}
Point p = new Point(3, 4);
System.out.println(p.x()); // 3
System.out.println(p); // Point[x=3, y=4]From that one line, Java creates:
- A
private finalfield for each component. - A constructor taking every component in order.
- An accessor method for each component, named like the component:
x(), notgetX(). equals()andhashCode()that compare all the components.- A readable
toString().
Immutability and equality
Record fields are final, and there are no setters, so a record can't be changed after it's created. To "change" one, create a new record with the new value.
Two records are equal when all their components are equal, even if they're different objects:
new Point(1, 2).equals(new Point(1, 2)) // trueThat makes records ideal as HashMap keys and HashSet items, and in tests, where you compare expected and actual values. One caution: if a component is a mutable object, such as a List, the list itself can still be changed. Store List.copyOf(list) to make it truly fixed.
Compact constructors and methods
To check values when a record is created, add a compact constructor: the record's name with no brackets. The assignments to the fields happen automatically after it runs:
record Product(String name, double price) {
Product {
if (price < 0) throw new IllegalArgumentException("price < 0");
name = name.trim();
}
}You can also add ordinary methods, static methods and static factory methods inside the braces. You can't add extra instance fields: everything a record holds must be listed in its header.
Records or classes?
| Use a record when | Use a class when |
|---|---|
| It mainly carries data | It has changing state, like a bank balance |
| Values shouldn't change after creation | It needs setters or extra hidden fields |
| Equality should mean "same values" | Identity matters: two objects are different even with the same values |
| Examples: Point, Money, SearchResult, DTOs | Examples: BankAccount, GameCharacter, Connection |
Records can implement interfaces, but they can't extend another class, and nothing can extend a record.
Records and pattern matching
Since Java 21, record patterns can take a record apart while checking its type. The components are bound to variables in one step:
Object shape = new Point(3, 4);
if (shape instanceof Point(int x, int y)) {
System.out.println(x + y); // 7
}Record patterns also work in switch, which pairs well with sealed interfaces: each case names a record type and pulls out exactly the components it needs. This style is especially readable for data that comes in a few fixed shapes, such as events, commands or API responses. The modern Java features lesson shows more examples.
Java Records Syntax
Record syntax
record Name(Type a, Type b) {
Name { // compact constructor
// validate a and b
}
Type method() { ... }
static Name of(...) { ... }
}
Name n = new Name(a, b);
n.a(); n.b();Java Records Summary
record Name(Type a, Type b) {}creates an immutable data class in one line.- Java generates the constructor, accessors like a(), equals, hashCode and toString.
- Records with the same component values are equal, so they work well as map keys.
- A compact constructor validates or normalises values; you can add methods but not extra fields.
- Use records for data carriers and classes for objects with changing state.
Java Records Examples
Each example shows the code, the output it produces and what each part does.
A record compared with a class
import java.util.HashSet;
import java.util.Set;
public class Main {
record Point(int x, int y) {
double distanceFromOrigin() {
return Math.sqrt(x * x + y * y);
}
}
public static void main(String[] args) {
Point a = new Point(3, 4);
Point b = new Point(3, 4);
System.out.println(a);
System.out.println("x is " + a.x());
System.out.println("Same object? " + (a == b));
System.out.println("Equal? " + a.equals(b));
System.out.println("Distance: " + a.distanceFromOrigin());
Set<Point> visited = new HashSet<>();
visited.add(a);
visited.add(b);
System.out.println("Unique points: " + visited.size());
}
}Point[x=3, y=4]
x is 3
Same object? false
Equal? true
Distance: 5.0
Unique points: 1Line-by-line explanation
- Line 5One line replaces fields, constructor, getters, equals, hashCode and toString.
- Lines 6 to 8Records can have their own methods, using the components directly.
- Lines 17 to 18Two separate objects, but equal because their values match.
- Lines 21 to 23The set treats them as the same point.
Validating with a compact constructor
public class Main {
record Money(long pence, String currency) {
Money {
if (pence < 0) {
throw new IllegalArgumentException("Amount can't be negative");
}
currency = currency.toUpperCase();
}
Money plus(Money other) {
return new Money(pence + other.pence, currency);
}
@Override
public String toString() {
return String.format("%d.%02d %s", pence / 100, pence % 100, currency);
}
}
public static void main(String[] args) {
Money price = new Money(1250, "gbp");
Money total = price.plus(new Money(399, "GBP"));
System.out.println(total);
try {
new Money(-5, "GBP");
} catch (IllegalArgumentException e) {
System.out.println("Rejected: " + e.getMessage());
}
}
}16.49 GBP
Rejected: Amount can't be negativeLine-by-line explanation
- Lines 3 to 8The compact constructor checks the amount and tidies the currency before the fields are set.
- Lines 10 to 12Instead of changing the record, plus returns a new one.
- Lines 14 to 17You can replace the generated toString with your own.
- Line 21"gbp" is stored as "GBP" thanks to the constructor.
Copies with a change, and records as keys
import java.util.HashMap;
import java.util.Map;
public class Main {
record Seat(char row, int number) {}
record Booking(String name, Seat seat, boolean paid) {
Booking markPaid() {
return new Booking(name, seat, true);
}
}
public static void main(String[] args) {
Booking first = new Booking("Ana", new Seat('C', 12), false);
Booking paid = first.markPaid();
System.out.println(first);
System.out.println(paid);
Map<Seat, String> taken = new HashMap<>();
taken.put(paid.seat(), paid.name());
System.out.println("C12 taken by " + taken.get(new Seat('C', 12)));
}
}Booking[name=Ana, seat=Seat[row=C, number=12], paid=false]
Booking[name=Ana, seat=Seat[row=C, number=12], paid=true]
C12 taken by AnaLine-by-line explanation
- Lines 8 to 10A "with" style method returns a copy with one value changed. The original is untouched.
- Lines 16 to 17Nested records print neatly too.
- Line 21A brand new Seat('C', 12) finds the entry, because records with equal values have equal hash codes.
Common Java Records Mistakes
Calling getX() on a record
Point p = new Point(1, 2);
int x = p.getX(); // no such methodint x = p.x();Trying to change a record
record Player(String name, int score) {}
player.score = 10; // score is finalplayer = new Player(player.name(), 10);Adding extra instance fields
record User(String name) {
private int loginCount; // not allowed in a record
}class User {
private final String name;
private int loginCount; // changing state belongs in a class
// ...
}Java Records in Real Projects
Records in practice
Modern Java code uses records for the data that flows through an application: API request and response bodies, database query results, configuration values, events and messages. Frameworks such as Spring and libraries like Jackson read and write records as JSON directly. Records also pair with streams and sorting, because their accessors work neatly as method references like Player::score.
Java Records FAQ
Can a record have setters?
No. Record fields are final, so they can't change after construction. Instead of a setter, add a method that returns a new record with one value changed, for example Product withPrice(double newPrice) { return new Product(name, newPrice); }.
Can a record have static fields and methods?
Yes. Static fields, static methods and static factory methods such as Point.origin() are all allowed. Only extra instance fields are forbidden, because all instance data must be listed in the record header.
Can records be used as JPA entities?
Not as entities. JPA providers such as Hibernate need entities they can create empty and then change, which conflicts with immutable records. Records work very well for data transfer objects, query results and API request and response bodies.
Do records work with JSON libraries?
Yes. Jackson has supported records since version 2.12, and Gson and other modern libraries handle them too. Each component becomes a JSON property with the same name, and the record's constructor is used when reading JSON back.
Which Java version do I need for records?
Records became a standard feature in Java 16, so any current long-term support release, such as Java 17, 21 or 25, supports them. Record patterns, which take records apart in instanceof and switch, need Java 21 or later.
Java Records Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
Complete the declaration of a record with a title and a year.
Film(String title, int year) {}
How do you read the title of Film f?
What does this print?
record Pair(int a, int b) {}
System.out.println(new Pair(1, 2).equals(new Pair(1, 2)));
Where do you validate a record's values?
Which is the best choice for a bank account whose balance changes?
Java Records Quiz
5 questions to check what you learned.
Which version of Java made records a standard feature?
- Java 8
- Java 11
- Java 16
- Java 21
Which methods does a record generate?
- Only a constructor
- A constructor, accessors, equals, hashCode and toString
- Setters and getters
- main
Can a record extend another class?
- Yes
- Only abstract classes
- Only other records
- No, but it can implement interfaces
How do you "change" a value in a record?
- Use a setter
- Create a new record with the new value
- Assign to the field
- Call update()
Why are records good HashMap keys?
- Equal values give equal hash codes, and the values can't change after insertion
- They're faster to create
- Records are sorted
- HashMap requires records