Introduction of Java Records


Records are another modern Java feature (finalised in Java 16) that dramatically reduce boilerplate when modelling simple immutable data carriers.
1. What problem do records solve?
In Java 8, if you wanted a class that just holds data (say User
with id
and name
), you had to write:
public class User {
private final String id;
private final String name;
public User(String id, String name) {
this.id = id;
this.name = name;
}
public String getId() { return id; }
public String getName() { return name; }
@Override
public boolean equals(Object o) { /* tedious */ }
@Override
public int hashCode() { /* tedious */ }
@Override
public String toString() { /* tedious */ }
}
That’s a lot of boilerplate for something that’s just a tuple of values.
2. Enter Records (Java 16+)
A record is a special class that is:
immutable by default (all fields are
final
)provides canonical constructor,
equals
,hashCode
, andtoString
automaticallycompact to declare
Example:
public record User(String id, String name) { }
This line generates:
private final fields
id
,name
constructor
User(String id, String name)
accessors
id()
,name()
equals
,hashCode
,toString
So instead of 50+ lines, you get 1.
3. When to use
Data transfer objects (DTOs) in REST APIs
Events in event-driven systems (e.g.,
PaymentCreated
,UserRegistered
)Keys in maps/sets (they get proper
equals
/hashCode
)Immutable configs
4. Advanced features
- Custom constructors
1. Canonical Constructor
A canonical constructor matches all record components.
Exists by default (generated by compiler).
You can override it to add validation or normalisation.
A. Explicit canonical constructor:
public record User(String id, String name) {
public User(String id, String name) {
if (id == null || id.isBlank())
throw new IllegalArgumentException("id required");
this.id = id;
this.name = name.trim();
}
}
B. Compact canonical constructor:
public record User(String id, String name) {
public User {
if (id == null || id.isBlank())
throw new IllegalArgumentException("id required");
name = name.trim(); // compiler auto-assigns to this.name
}
}
2. Non-Canonical (Overloaded) Constructors
Provide extra ways to create an object (convenience).
Must delegate to the canonical constructor using
this(...)
.Useful for default values or alternative parameter types.
Example:
public record Point(int x, int y) {
// default y = 0
public Point(int x) {
this(x, 0); // must delegate
}
// convert from double
public Point(double x, double y) {
this((int) x, (int) y);
}
}
- Additional methods
You can add extra behavior:
public record Point(int x, int y) {
public int distanceFromOrigin() {
return (int) Math.sqrt(x*x + y*y);
}
}
- Implements interfaces
public record CardPayment(String number, double amount) implements PaymentMethod { }
- Pattern matching synergy: Works seamlessly with
switch
/instanceof
(especially with sealed hierarchies).
5. Limitations
Records are implicitly final: you cannot extend them.
All fields are shallowly immutable (the reference is final, but if you store a mutable object, it can still be changed).
Not a replacement for full domain models—best used for data carriers.
6. Example with flow
public record Employee(String name, int age) {
// Canonical constructor (with validation)
public Employee {
if (age < 18) throw new IllegalArgumentException("Too young");
name = name.toUpperCase();
}
// Non-canonical constructor (default age)
public Employee(String name) {
this(name, 25); // calls canonical constructor
}
}
public class Main {
public static void main(String[] args) {
new Employee("Alice"); // calls non-canonical, which calls canonical
new Employee("Bob", 30); // calls canonical directly
}
}
Execution order:
new Employee("Alice")
→ non-canonical →this(name, 25)
→ canonical runs validation/normalization → object created.new Employee("Bob", 30")
→ canonical runs directly.
Subscribe to my newsletter
Read articles from Mohit jain directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Mohit jain
Mohit jain
Oracle Certified Java Developer with 7+ years of experience specializing in backend development, microservices architecture, and cloud-based solutions. Proven expertise in designing scalable systems, optimizing performance, and mentoring teams to enhance productivity. Passionate about building high-performance applications using Java, Spring Boot, Kafka, and cloud technologies (AWS/GCP)