Skip to content

Best Lombok Annotations for Spring Boot Projects

Problem

When I write Spring Boot services, I face a lot of boilerplate code:

UserService.java (verbose approach)
@Service
public class UserService {
private static final Logger log = LoggerFactory.getLogger(UserService.class);
private final UserRepository userRepository;
private final EmailService emailService;
private final AuditService auditService;
// Boilerplate constructor - 8 lines for 3 dependencies
public UserService(UserRepository userRepository,
EmailService emailService,
AuditService auditService) {
this.userRepository = userRepository;
this.emailService = emailService;
this.auditService = auditService;
}
public User createUser(String email, String name) {
log.info("Creating user with email: {}", email);
// ...
}
}

Every service has:

  • Manual logger initialization
  • Repetitive constructor for dependency injection
  • Getter/setter boilerplate for entities

I wanted to reduce this clutter.

Environment

  • Spring Boot 3.x
  • Lombok 1.18.x
  • IntelliJ IDEA with Lombok plugin

What happened?

I tried using @Data everywhere to eliminate boilerplate:

Product.java (problematic)
@Entity
@Data // Includes @EqualsAndHashCode!
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private BigDecimal price;
@OneToMany(mappedBy = "product", fetch = FetchType.LAZY)
private Set<Order> orders;
}

But this caused issues:

  • equals() triggered lazy loading of orders
  • hashCode() changed when ID was assigned
  • Duplicate entities appeared in Sets

I needed to understand which annotations to use and when.

How to solve it?

I found four key annotations that work best in Spring Boot:

1. @RequiredArgsConstructor for Dependency Injection

UserService.java (clean approach)
@Service
@Slf4j
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
private final EmailService emailService;
private final AuditService auditService;
// No manual constructor needed!
// Lombok generates it for all final fields
public User createUser(String email, String name) {
log.info("Creating user with email: {}", email);
return User.builder()
.email(email)
.name(name)
.createdAt(LocalDateTime.now())
.active(true)
.build();
}
}

Spring automatically uses Lombok’s generated constructor. No @Autowired needed when there’s only one constructor.

2. @Slf4j for Logging

One annotation replaces this:

Before @Slf4j
private static final Logger log = LoggerFactory.getLogger(UserService.class);

With this:

After @Slf4j
@Slf4j
public class UserService {
// 'log' field is auto-generated
}

3. @Builder for Complex Objects

Instead of 50+ lines of manual builder code:

User.java (with @Builder)
@Getter
@Setter
@Builder
public class User {
private String email;
private String name;
@Builder.Default
private LocalDateTime createdAt = LocalDateTime.now();
@Builder.Default
private boolean active = true;
}

Usage:

Builder usage
User user = User.builder()
.name("Test User")
.build();

4. Selective @Getter/@Setter for JPA Entities

Avoid @Data on JPA entities:

Customer.java (safe for JPA)
@Entity
@Getter
@Setter
@NoArgsConstructor
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String email;
private String name;
@OneToMany(mappedBy = "customer", cascade = CascadeType.ALL)
private List<Order> orders = new ArrayList<>();
// Manual equals/hashCode for JPA best practices
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Customer)) return false;
Customer customer = (Customer) o;
return id != null && id.equals(customer.id);
}
@Override
public int hashCode() {
return getClass().hashCode();
}
}

The reason

I think the key insights are:

  1. @Data includes too much: It adds equals(), hashCode(), toString() which cause JPA issues.

  2. Constructor injection is Spring’s recommended approach: @RequiredArgsConstructor makes it effortless.

  3. Final fields enforce immutability: Dependencies cannot be changed after initialization, improving thread safety.

  4. Selective annotations give control: Use exactly what you need, not everything.

Summary

In this post, I showed the best Lombok annotations for Spring Boot. The key point is: use @RequiredArgsConstructor for dependency injection, @Slf4j for logging, @Builder for complex objects, and selective @Getter/@Setter instead of broad @Data. This reduces boilerplate while maintaining clean, testable code.

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