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:
@Valuepublic class UserResponse { Long id; String username; String email;}And then I saw this pattern:
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:
org.hibernate.HibernateException: No default constructor for entityI had used a Record for a JPA entity:
@Entitypublic 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:
public record UserResponse( Long id, String username, String email) {}@Entity@Table(name = "users")@Getter@Setter@NoArgsConstructor@AllArgsConstructorpublic 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:
@Builderpublic record CreateOrderRequest( Long userId, @Singular List<Long> productIds, String shippingAddress, @Builder.Default String billingAddress = null, @Builder.Default String couponCode = null) {}Usage:
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:
@Service@RequiredArgsConstructor@Slf4jpublic 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:
-
Records are immutable by design: No setters, final fields, no no-arg constructor. JPA entities need setters for Hibernate to update entity state.
-
Hibernate needs proxy support: For lazy loading, Hibernate creates proxies at runtime. Proxies require non-final classes with accessible constructors.
-
Lombok generates at compile-time: It produces regular Java bytecode. Hibernate sees a normal mutable class, not an annotation.
-
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