Skip to content

Lombok vs Java Records: Which for Spring Boot?

Problem

When I started a new Spring Boot project, I faced a common dilemma: should I use Lombok annotations or the newer Java Records for my data classes?

I had code like this:

UserResponse.java (old approach)
@Value
public class UserResponse {
Long id;
String username;
String email;
}

And then I saw this pattern:

ProductResponse.java (new approach)
public record ProductResponse(
Long id,
String name,
BigDecimal price
) {}

Both achieve immutability. Both reduce boilerplate. But which approach should I use?

Environment

  • Java 17+ (Records available since Java 16)
  • Spring Boot 3.x
  • Lombok 1.18.x
  • Hibernate/JPA for persistence

What happened?

I tried migrating everything to Records. I replaced @Value classes with Records across my project. It felt cleaner to use a native language feature.

Then I hit this error:

Runtime Error
org.hibernate.HibernateException: No default constructor for entity

I had used a Record for a JPA entity:

UserEntity.java (WRONG)
@Entity
public record UserEntity( // COMPILE/RUNTIME ERROR!
@Id Long id,
String username,
String email
) {}

Hibernate couldn’t instantiate the entity proxy. Records are implicitly final and have no no-arg constructor.

So I had to reconsider my approach.

How to solve it?

I needed to understand when each approach works. After research and testing, I found a clear pattern:

Use Java Records for:

  • Simple immutable DTOs
  • API response/request models
  • Value objects for domain modeling
  • Configuration properties classes
  • Returning compound values from methods

Keep Lombok for:

  • JPA/Hibernate entities (require setters)
  • Complex builder patterns with @Builder
  • Constructor injection with @RequiredArgsConstructor
  • Logging with @Slf4j

Here’s my solution code:

UserResponse.java (Record - correct for DTOs)
public record UserResponse(
Long id,
String username,
String email
) {}
UserEntity.java (Lombok - correct for JPA)
@Entity
@Table(name = "users")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class UserEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String username;
@Column(nullable = false)
private String email;
}

For complex construction, I use Lombok @Builder with Records:

CreateOrderRequest.java (Record + Builder)
@Builder
public record CreateOrderRequest(
Long userId,
@Singular List<Long> productIds,
String shippingAddress,
@Builder.Default String billingAddress = null,
@Builder.Default String couponCode = null
) {}

Usage:

Usage example
CreateOrderRequest request = CreateOrderRequest.builder()
.userId(1L)
.productId(100L) // @Singular allows individual adds
.productId(200L)
.shippingAddress("123 Main St")
.build();

For Spring services, I keep @RequiredArgsConstructor:

OrderService.java
@Service
@RequiredArgsConstructor
@Slf4j
public class OrderService {
private final OrderRepository orderRepository;
private final PaymentService paymentService;
public void processOrder(CreateOrderRequest request) {
log.info("Processing order for user: {}", request.userId());
}
}

The reason

I think the key reason for this distinction is:

  1. Records are immutable by design: No setters, final fields, no no-arg constructor. JPA entities need setters for Hibernate to update entity state.

  2. Hibernate needs proxy support: For lazy loading, Hibernate creates proxies at runtime. Proxies require non-final classes with accessible constructors.

  3. Lombok generates at compile-time: It produces regular Java bytecode. Hibernate sees a normal mutable class, not an annotation.

  4. Records eliminate external dependencies: For simple DTOs, Records are native Java. No IDE plugin needed, no annotation processor.

Summary

In this post, I showed how to choose between Lombok and Java Records in Spring Boot. The key point is: use Records for simple immutable DTOs, keep Lombok for JPA entities, builder patterns, and constructor injection. This hybrid approach leverages native Java features while preserving framework compatibility.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments