Comprehensive Guide to Records in Java
Target Versions:
- Preview: Java 14 (JEP 359), Java 15 (JEP 384)
- Final / Standard: Java 16 (JEP 395)Records provide a compact syntax for immutable, data-carrier classes.
They eliminate boilerplate (equals,hashCode,toString, constructors, getters).
They integrate naturally with pattern matching, sealed classes, and modern Java.
1. What Are Records?
A record is a special class designed for immutable data.
When you declare:
record Point(int x, int y) {}
Java automatically generates:
private final int x;private final int y;- A canonical constructor
- Accessor methods:
int x()andint y() equals(Object)hashCode()toString()
Records are always:
- final (cannot be subclassed)
- shallowly immutable
- transparent data carriers
2. Record Declaration Syntax
record User(String name, int age) {}
Equivalent to a full, immutable class with boilerplate.
Records may also be generic:
record Pair<A, B>(A first, B second) {}
3. Accessor Methods
Record components automatically create accessor methods:
record User(String name, int age) {}
User u = new User("Ali", 30);
u.name(); // "Ali"
u.age(); // 30
No getName() or getAge()—accessor names match component names exactly.
4. Immutability Rules
- Record components →
private finalfields - Records → implicitly
finalclass - No setters
- Cannot add instance fields
Example of illegal code:
record User(String name) {
int extra; // ❌ instance field not allowed
}
Records enforce shallow immutability:
Referenced objects can still be mutated unless you make them immutable.
5. Canonical Constructor (Automatically Generated)
Given:
record User(String name, int age) {}
Java automatically generates:
public User(String name, int age) {
this.name = name;
this.age = age;
}
6. Compact Constructor (For Validation)
You can override the canonical constructor using a compact constructor:
record User(String name, int age) {
public User {
if (age < 0) throw new IllegalArgumentException("Age must be >= 0");
}
}
Notes:
- No parameter parentheses
- No explicit assignments (
this.name = name) allowed - Assignments happen automatically after the constructor body
7. Full Canonical Constructor
If you need custom assignment logic, use the full form:
record User(String name, int age) {
public User(String name, int age) {
this.name = name.trim();
this.age = age;
}
}
You must manually assign all components.
8. Adding Methods
Records can have normal methods:
record User(String name, int age) {
public String greeting() {
return "Hello " + name;
}
}
9. Static Members Allowed
Static fields and methods are allowed:
record Config(String key) {
static String APP = "MyApp";
static int version() { return 1; }
}
Instance fields (non-static) are not allowed.
10. Implementing Interfaces
Records can implement interfaces:
record User(String name, int age) implements Comparable<User> {
public int compareTo(User other) {
return this.name.compareTo(other.name);
}
}
Records cannot extend classes (they implicitly extend java.lang.Record).
11. Nested Records
Records can be nested:
class Outer {
record Inner(int x, int y) {}
}
12. Validation & Normalization
Compact constructors allow validation:
record Range(int start, int end) {
public Range {
if (start > end)
throw new IllegalArgumentException("start <= end");
}
}
Normalization example:
record Name(String first, String last) {
public Name {
first = first.trim();
last = last.trim();
}
}
13. Serialization
Records integrate naturally with Java serialization:
- Field-based serialization
- Record structure is preserved
- Components must be serializable
14. Comparison With Lombok @Data or IDE-Generated Classes
Records are better because:
- No external library required
- Always immutable
- Compiler-enforced consistency
- Fewer moving parts
- Clear semantics
- Pattern matching integration
Lombok-generated classes can mutate; records prevent this.
15. Integration With Pattern Matching
Records work great with:
15.1. instanceof pattern matching
if (obj instanceof User(String name, int age)) {
// destructuring via record pattern
}
15.2. Pattern matching for switch
String result = switch(obj) {
case User(String n, int a) -> "User: " + n + " (" + a + ")";
default -> "Unknown";
};
15.3. Record Patterns (Java 21+)
record Point(int x, int y) {}
switch (p) {
case Point(int x, int y) -> System.out.println(x + ", " + y);
}
16. Reflection and Record Metadata
Reflection support:
RecordComponent[] components = User.class.getRecordComponents();
for (var c : components) {
System.out.println(c.getName() + " : " + c.getType());
}
17. Common Pitfalls
17.1. Mutability of Referenced Objects
Records are shallowly immutable:
record User(String name, List<String> roles) {}
var u = new User("Ali", new ArrayList<>());
u.roles().add("Admin"); // allowed — mutates the list
Use immutable collections inside records if needed.
17.2. Compact Constructor Limitations
This does not modify the assigned value:
record User(String name) {
public User {
name = "Ali"; // changes *local* copy, not the final field
}
}
Correct:
- Use full canonical constructor
- Or assign in the compact constructor only if intent is normalization (not replacement)
17.3. Attempt to Add Instance Fields
Not allowed:
record A(int x) {
int y; // ❌
}
17.4. Overriding equals/hashCode Incorrectly
Usually unnecessary and risky—generated versions are correct and consistent.
18. Best Practices
✔ Use records for immutable data models
✔ Keep them small and focused
✔ Use compact constructors for validation only
✔ Prefer immutable collections inside records
✔ Avoid overriding auto-generated methods
✔ Integrate records with pattern matching and sealed types
Avoid:
❌ Heavy business logic inside records
❌ Mutating internal state via mutable collections
❌ Modeling domain entities that require identity (use classes instead)
19. Interview Questions (With Answers)
Q1. What problem do records solve?
They eliminate boilerplate for immutable data classes (equals, hashCode, toString, constructors).
Q2. Are records immutable?
Yes, shallowly immutable: fields are final, but referenced objects may be mutable.
Q3. Can records extend classes?
No. Records implicitly extend java.lang.Record.
Q4. Can records implement interfaces?
Yes.
Q5. Can records have additional instance fields?
No — only components define instance state.
Q6. What constructors can records define?
- Canonical constructor
- Compact constructor
- Auxiliary constructors (delegating to canonical)
Q7. Do records work with pattern matching?
Yes — they integrate deeply with pattern matching (switch, instanceof, record patterns).
20. Summary
Records are one of the most powerful improvements to Java:
- Concise, immutable, boilerplate-free data carriers
- Ideal for DTOs, configuration objects, pure values
- Integrated with pattern matching and sealed types
- Finalized in Java 16, widely adopted in modern Java
This guide provides everything needed for interviews and real-world development.