What is Record in Java and since which version are they available
Record allows you to declare a class in a single line, which automatically receives:
🟢 Junior Level
Record is a special type of class in Java designed for storing immutable data. It appeared as a preview feature in Java 14 (JEP 359) and became a full standard feature in Java 16 (JEP 395).
Record allows you to declare a class in a single line, which automatically receives:
- private
finalfields - a public constructor
- getters (without the
getprefix) equals(),hashCode()andtoString()
Example:
// Instead of 50+ lines of code — one line
public record User(String name, int age, String email) {}
// Usage
User user = new User("John", 25, "john@example.com");
System.out.println(user.name()); // John
System.out.println(user.age()); // 25
When to use:
- DTO (Data Transfer Objects)
- Classes for storing query results from DB
- Keys in collections (HashMap, HashSet)
- Returning multiple values from a method
- Any situation where you need a simple immutable class
🟡 Middle Level
How it works
Record is not just “syntactic sugar” — it’s a special kind of class with strict constraints:
// What you write:
public record Point(int x, int y) {}
// What the compiler generates:
public final class Point extends java.lang.Record {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public int x() { return x; }
public int y() { return y; }
@Override
public boolean equals(Object o) { /* auto-generated */ }
@Override
public int hashCode() { /* auto-generated */ }
@Override
public String toString() { /* auto-generated */ }
}
Practical Application
1. DTO in Spring Boot:
public record CreateUserRequest(
@NotBlank String name,
@Email String email,
@Min(18) int age
) {}
@RestController
public class UserController {
@PostMapping("/users")
public ResponseEntity<UserResponse> createUser(
@Valid @RequestBody CreateUserRequest request
) {
// request.name(), request.email() — access fields
}
}
2. Returning multiple values:
public record CalculationResult(int result, int operationTimeMs) {}
public CalculationResult calculate() {
// ... complex logic
return new CalculationResult(42, 150);
}
3. Pattern matching in switch (Java 21+):
public sealed interface Shape permits Circle, Rectangle {}
public record Circle(double radius) implements Shape {}
public record Rectangle(double width, double height) implements Shape {}
public double getArea(Shape shape) {
return switch (shape) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Rectangle r -> r.width() * r.height();
};
}
Common Mistakes
- Mistake: trying to make Record mutable
// ❌ CANNOT — fields are always final public record BadRecord() { public int mutableField = 0; // compilation error, unless static } - Mistake: mutable components in Record
// ⚠️ Caution — array is mutable! public record DataWithArray(int[] values) {} DataWithArray d1 = new DataWithArray(new int[]{1, 2, 3}); d1.values()[0] = 99; // can be changed! // ✅ Better — use List.of() or make a copy public record SafeData(List<Integer> values) {} - Mistake: null in Record
// Record allows null by default public record User(String name) {} User user = new User(null); // OK, but may lead to NPE // ✅ Solution — null check in constructor (Java 16+) public record User(String name) { public User { Objects.requireNonNull(name, "Name cannot be null"); } }
Comparison with Alternatives
| Approach | Pros | Cons |
|---|---|---|
| Record | Minimal code, immutable, auto-generation | Cannot inherit, only final fields |
| Lombok @Data | More flexibility, mutable/immutable | Lombok dependency, IDE issues |
| Manual class | Full control | Lots of boilerplate, errors in equals/hashCode |
| Class with @Value | Immutable, clear | More code than Record |
🔴 Senior Level
Internal Implementation
Record is not just syntactic sugar — it’s a new data model in the JVM.
Key JEPs:
- JEP 359: Records (Preview, Java 14)
- JEP 384: Records (Second Preview, Java 15)
- JEP 395: Records (Final, Java 16)
Internal structure:
// Compiler adds hidden attributes:
// - ACC_RECORD flag in class file
// - RecordComponents attribute in constant pool
// At runtime you can get components via reflection:
RecordComponent[] components = Point.class.getRecordComponents();
for (RecordComponent rc : components) {
System.out.println(rc.getName()); // "x", "y"
System.out.println(rc.getType()); // int, int
System.out.println(rc.getAccessor()); // Method reference
}
Class file structure:
ClassFile {
u2 access_flags; // ACC_RECORD | ACC_FINAL
u2 this_class;
u2 super_class; // java/lang/Record
...
Record_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 components_count;
record_component_info components[];
}
}
Architectural Trade-offs
Record vs regular class:
| Aspect | Record | Regular class |
|---|---|---|
| Inheritance | Not allowed (implicit final) | Allowed |
| Fields | Only final | Any |
| Interfaces | Can implement | Can |
| sealed | Can | Can |
| Annotations | On fields, constructor | On anything |
| Serialization | Own logic (JEP 445) | Standard |
Why Record cannot be extended:
java.lang.Record — special superclass not available for direct extends.
This is done for:
1. Immutability guarantee
2. Predictable equals/hashCode
3. JVM optimization (value types compatibility)
4. Pattern matching support
Edge Cases
1. Generic Records:
public record ApiResponse<T>(int status, T data, String message) {}
// Usage
ApiResponse<List<User>> response = new ApiResponse<>(200, users, "OK");
ApiResponse<Optional<User>> maybeUser = new ApiResponse<>(404, Optional.empty(), "Not found");
2. Recursive Records (for linked structures):
public record ListNode<T>(T value, ListNode<T> next) {}
ListNode<String> list = new ListNode<>("A",
new ListNode<>("B",
new ListNode<>("C", null)));
3. Records with annotations:
public record User(
@JsonProperty("user_name") @NonNull String name,
@Min(0) @Max(150) int age
) {
// Annotation on component applies to:
// - field
// - constructor parameter
// - accessor method
}
// For precise control — annotations on target elements:
public record Config(
String key
) {}
// In Java, annotations on Record components apply to field, parameter, and accessor.
// For targeted application, use meta-annotations @Target({FIELD, METHOD, PARAMETER}).
4. Records and Serialization (Java 21+):
// Record serialization: during deserialization the canonical constructor is called,
// not readObject. This is standard Java serialization (not JEP 445).
record SerializableData(String name, int value) implements Serializable {}
// JVM automatically validates:
// 1. All final fields are initialized
// 2. Invariants are preserved
// 3. Compact constructor can be used for validation
Performance
Memory:
Record vs Class with same functionality:
- Object header: 12-16 bytes (depends on JVM)
- Fields: as usual (int = 4, long = 8, reference = 4-8)
- Lombok generates code at compile time — no runtime overhead in either case.
- No extra fields for equals/hashCode cache
Execution time:
equals() / hashCode() auto-generated:
- Compiler creates optimal implementation
- Uses Objects.hash() or Arrays.hashCode()
- Performance comparable to manual implementation
- JIT can inline better (final fields)
Benchmark (JMH, Java 21):
// Approximate values (JMH, Java 21). Depend on JVM and hardware.
Operation | Record | Lombok @Value | Manual class
-------------------|--------|---------------|--------------
equals() | 15 ns | 15 ns | 14 ns
hashCode() | 12 ns | 13 ns | 12 ns
toString() | 45 ns | 48 ns | 46 ns
Object creation | 8 ns | 8 ns | 8 ns
Difference < 5% — Record is not inferior in performance
Production Experience
Real case — migrating DTOs to Records:
// Before (Spring Boot 2.x, Lombok):
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserDto {
private String name;
private String email;
private int age;
}
// After (Spring Boot 3.x, Java 17+):
public record UserDto(String name, String email, int age) {}
// Result:
// - Code reduced from ~15 lines to 1
// - Removed Lombok dependency for these classes
// - DTOs became immutable (protection from accidental changes)
// - Jackson serializes automatically (via constructor)
Issue with Jackson and Records:
// Jackson 2.12+ supports Records natively
// But there may be nuances:
public record Order(@JsonProperty("order_id") String id) {}
// ✅ Works out of the box in Spring Boot 2.4+
// ⚠️ In older versions you need:
// 1. Update Jackson to 2.12+
// 2. Or add @JsonCreator on canonical constructor
Monitoring
Debugging Records:
# Decompile .class file
javap -p UserRecord.class
# Output will show:
# final class UserRecord extends java.lang.Record
# private final java.lang.String name
# private final int age
# public UserRecord(java.lang.String, int)
# public java.lang.String name()
# public int age()
Reflection for inspection:
public static void inspectRecord(Object record) {
Class<?> clazz = record.getClass();
if (!clazz.isRecord()) {
throw new IllegalArgumentException("Not a record");
}
RecordComponent[] components = clazz.getRecordComponents();
System.out.println("Record: " + clazz.getSimpleName());
for (RecordComponent rc : components) {
Method accessor = rc.getAccessor();
Object value = accessor.invoke(record);
System.out.println(" " + rc.getName() + " = " + value);
}
}
Future Trends
Java 21+:
// Record Patterns (JEP 440) — Pattern matching for Record
Point p = new Point(42, 0);
if (p instanceof Point(int x, int y)) {
System.out.println(x + y); // 42
}
// Switch with Record Patterns
double calcArea(Shape s) {
return switch (s) {
case Circle(double r) -> Math.PI * r * r;
case Rectangle(double w, double h) -> w * h;
case Triangle(double b, double h) -> 0.5 * b * h;
};
}
// Sealed + Records + Pattern Matching = powerful combo
public sealed interface Expr
permits Num, Add, Mul {}
public record Num(int value) implements Expr {}
public record Add(Expr left, Expr right) implements Expr {}
public record Mul(Expr left, Expr right) implements Expr {}
int eval(Expr e) {
return switch (e) {
case Num(int n) -> n;
case Add(Expr l, Expr r) -> eval(l) + eval(r);
case Mul(Expr l, Expr r) -> eval(l) * eval(r);
};
}
Best Practices for Highload
// 1. Use Records for immutable DTO
public record OrderDto(String id, Instant createdAt, BigDecimal amount) {}
// 2. Validation in compact constructor
public record UserId(String value) {
public UserId {
if (value == null || value.isBlank()) {
throw new IllegalArgumentException("Invalid ID");
}
if (value.length() > 36) {
throw new IllegalArgumentException("ID too long");
}
}
}
// 3. Records as cache keys — ideal (immutable + hashCode)
public record CacheKey(String tenantId, String entityType, String entityId) {}
Map<CacheKey, Object> cache = new ConcurrentHashMap<>();
// 4. Use for event sourcing
public record DomainEvent(
UUID eventId,
String aggregateId,
String eventType,
Instant occurredAt,
Map<String, Object> payload
) {}
🎯 Interview Cheat Sheet
Must know:
- Records appeared as preview in Java 14 (JEP 359), became stable in Java 16 (JEP 395)
- Record is a special type of class that inherits
java.lang.Record - Auto-generates: canonical constructor, accessors,
equals(),hashCode(),toString() - Accessors are named after the field:
user.name(), NOTuser.getName() - Record is ideal for DTO, HashMap keys, value objects
- Record does not support inheritance — implicit final
- Starting from Java 21, pattern matching for Records is available (JEP 440)
Common follow-up questions:
- Since which version are Records stable? — Java 16 (JEP 395), preview — Java 14
- Can you make a Record mutable? — No, all fields are automatically final
- How does Record work with Jackson? — Jackson 2.12+ supports natively via canonical constructor
- How does Record differ from Lombok @Value? — Record is a language standard, less code, no Lombok dependency
Red flags (DO NOT say):
- ❌ “Record is just syntactic sugar for Lombok” — Record is a full data model in the JVM
- ❌ “You can inherit Record” — Record is implicit final, inheritance is forbidden
- ❌ “Record has get/set methods” — accessors without get prefix:
name()instead ofgetName() - ❌ “Record is slower than a regular class” — performance is identical (difference < 5%)
Related topics:
- [[2. What are the main differences between Record and regular class]]
- [[5. What methods are automatically generated for a Record]]
- [[9. Are Record fields final]]
- [[10. Can you use Record as a key in HashMap]]