Introduction
If Java developers were asked one question for the last twenty years, it would probably be:
“Why do I have to write so much boilerplate code?”
For years, enterprise developers solved this problem in three different ways:
- Writing Plain Old Java Objects (POJOs)
- Using Lombok annotations
- Using Java Records (introduced in modern Java)
Today, almost every Spring Boot application uses one of these approaches.
Understanding when to use each one is far more important than simply learning the Record syntax.
This article explains not only how Records work, but also how they compare with Lombok, and where each approach fits into modern enterprise applications.
Learning Objectives
By the end of this article, you will be able to:
- Understand why Records were introduced.
- Compare POJOs, Lombok and Records.
- Understand immutability.
- Create constructors and validation.
- Use Records with Spring Boot.
- Use Records with Jackson.
- Design REST APIs using Records.
- Understand why Records should not replace JPA entities.
- Decide when Lombok is still the better choice.
The Evolution of Java Data Models
The evolution of Java data modeling can be summarized as:
Java POJO
↓
Lombok
↓
Java Records
Each step reduced boilerplate while improving readability.
Traditional POJO
public class EmployeeDto {
private final Long id;
private final String name;
private final String department;
public EmployeeDto(Long id,
String name,
String department) {
this.id = id;
this.name = name;
this.department = department;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
public String getDepartment() {
return department;
}
@Override
public boolean equals(Object obj) {
...
}
@Override
public int hashCode() {
...
}
@Override
public String toString() {
...
}
}
This class contains mostly boilerplate.
Lombok
Lombok dramatically reduced the amount of code developers needed to write.
The same DTO becomes:
@Getter
@AllArgsConstructor
@EqualsAndHashCode
@ToString
public class EmployeeDto {
private final Long id;
private final String name;
private final String department;
}
Or even:
@Data
@AllArgsConstructor
public class EmployeeDto {
private Long id;
private String name;
private String department;
}
Compared to a traditional POJO, Lombok removes a significant amount of repetitive code.
However, it introduces a compile-time dependency through annotation processing.
Java Records
The same DTO using Records:
public record EmployeeDto(
Long id,
String name,
String department) {
}
Three lines.
No annotations.
No generated source code.
No annotation processor.
Comparing the Three Approaches
| Feature | POJO | Lombok | Record |
|---|---|---|---|
| Boilerplate | High | Low | Minimal |
| Constructor | Manual | Generated | Generated |
| Getters | Manual | Generated | Generated |
| equals()/hashCode() | Manual | Generated | Generated |
| toString() | Manual | Generated | Generated |
| Mutable | Yes | Usually | No |
| Immutable | Manual | Possible | Yes |
| Requires Annotation Processor | No | Yes | No |
| Part of JDK | Yes | No | Yes |
What Does the Compiler Generate?
For a Record:
public record Customer(
Long id,
String name) {
}
The compiler automatically generates:
- Private final fields
- Canonical constructor
- Accessor methods
- equals()
- hashCode()
- toString()
Example:
Customer customer =
new Customer(1L, "Rahul");
System.out.println(customer.name());
Notice the generated accessor:
customer.name()
instead of
customer.getName()
Records Are Immutable
Records are immutable by design.
Customer customer =
new Customer(1L, "Rahul");
customer.name = "John";
Compilation error.
Immutability makes Records naturally suitable for DTOs, events, and value objects.
Compact Constructors
Validation remains possible.
public record Customer(
Long id,
String name){
public Customer{
if(name == null ||
name.isBlank()){
throw new IllegalArgumentException(
"Name cannot be blank");
}
}
}
The compiler performs field assignment automatically after validation.
Additional Methods
Records can contain behavior.
public record Money(
BigDecimal amount,
String currency){
public boolean isPositive(){
return amount.signum() > 0;
}
}
Records are not restricted to getters.
Records with Jackson
Modern versions of Jackson support Records directly.
Record
↓
Jackson
↓
JSON
and
JSON
↓
Jackson
↓
Record
No setters or default constructors are required.
Records in Spring Boot
Records work exceptionally well for:
- Request DTOs
- Response DTOs
- Configuration Properties
- Kafka Events
- RabbitMQ Messages
- Immutable Value Objects
- REST Contracts
Example:
public record CreateCustomerRequest(
String firstName,
String lastName,
String email){
}
Records and JPA
This is one of the biggest misconceptions.
Avoid:
@Entity
public record Customer(...){
}
JPA entities generally require:
- Mutable state
- Proxy generation
- Lazy loading
- Lifecycle callbacks
- No-argument constructors
Records intentionally provide:
- Final components
- Immutability
- Canonical constructors
These goals conflict with traditional JPA entity requirements.
Recommended architecture:
Database
↓
JPA Entity
↓
Mapper
↓
Record DTO
↓
REST API
Records vs Lombok
This is the question most enterprise developers ask.
Use Records When
✔ Building REST request DTOs.
✔ Building REST response DTOs.
✔ Creating Kafka or RabbitMQ events.
✔ Creating immutable configuration objects.
✔ Modeling value objects.
✔ Creating API contracts.
Example:
public record OrderResponse(
UUID orderId,
BigDecimal amount,
Instant createdAt){
}
Records express intent clearly: this object is immutable.
Use Lombok When
Lombok is still valuable for scenarios requiring mutability or additional code generation.
Examples include:
- JPA entities
- Builder pattern
- Mutable domain objects
- Legacy applications
- Classes requiring many optional fields
Example:
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Customer {
@Id
private Long id;
private String name;
private String email;
}
Features such as @Builder, @With, and selective accessor generation remain valuable and are not replaced by Records.
Records vs Lombok Builder
Suppose a class contains fifteen fields.
Creating a Record constructor becomes cumbersome:
new CustomerResponse(
id,
name,
email,
phone,
address,
...
);
Lombok Builder:
Customer customer =
Customer.builder()
.name("Rahul")
.email("rahul@example.com")
.build();
For complex object creation, builders are often more readable than long constructors.
Enterprise Decision Matrix
| Scenario | Recommended Choice |
|---|---|
| REST Request DTO | ✅ Record |
| REST Response DTO | ✅ Record |
| Kafka Event | ✅ Record |
| RabbitMQ Event | ✅ Record |
| Configuration Properties | ✅ Record |
| Immutable Value Object | ✅ Record |
| JPA Entity | ✅ Lombok / Regular Class |
| Mutable Domain Object | ✅ Lombok |
| Builder Pattern | ✅ Lombok |
| Legacy Spring Boot Project | ✅ Lombok |
| Complex Object Construction | ✅ Lombok Builder |
Common Mistakes
Replacing Every Lombok Class with a Record
Not every class should become a Record.
Choose the abstraction that reflects the lifecycle and mutability of the object.
Using Records for Entities
Entities and Records have fundamentally different design goals.
Assuming Records Replace Lombok
They do not.
Records eliminate much of the need for Lombok in immutable DTOs, but Lombok remains valuable for mutable classes, builders, and many enterprise patterns.
Best Practices
✔ Prefer Records for immutable API contracts.
✔ Continue using Lombok for JPA entities.
✔ Use Lombok Builder for complex object creation.
✔ Validate Record components using compact constructors.
✔ Keep entities and DTOs separate.
✔ Let Records model data, not business workflows.
Interview Questions
Do Records replace Lombok?
No.
Records replace much of Lombok’s usage for immutable DTOs, but Lombok remains useful for builders, mutable objects, and JPA entities.
Should JPA entities be converted to Records?
Generally no.
JPA entities require capabilities that Records intentionally do not provide.
When is Lombok Builder preferable?
When constructing complex objects with many optional fields or when constructor parameter lists become difficult to read.
When should Records be preferred?
For immutable data carriers such as REST DTOs, messaging events, configuration objects, and value objects.
Summary
Records are one of the most important additions to modern Java because they eliminate boilerplate while making immutability the default. They integrate seamlessly with Spring Boot, Jackson, and messaging frameworks, making them an excellent choice for DTOs, API contracts, configuration objects, and events.
However, Records do not replace every Java class. JPA entities, mutable domain models, and complex object construction often remain better served by Lombok or traditional classes. Modern enterprise applications frequently use both technologies:
- Records for immutable data transfer and messaging.
- Lombok for entities, builders, and mutable domain models.
Choosing between them is not a matter of which is “better,” but of selecting the right tool for the specific responsibility within your architecture.