Lombok: Reduce Java Boilerplate Code
Complete guide to Project Lombok for Java. Learn how to eliminate boilerplate code with annotations like @Data, @Builder, and @RequiredArgsConstructor.
Moshiour Rahman
Advertisement
Introduction
Java is powerful, but it can be verbose. Writing getters, setters, constructors, and equals/hashCode methods for every class is tedious. Enter Lombok - a library that generates this boilerplate code at compile time using annotations.
Project Setup
Using Spring Initializr
spring init --dependencies=lombok --package=com.techyowls lombok-demo
Gradle Configuration
Add to your build.gradle:
dependencies {
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
Maven Configuration
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
Note: Install the Lombok plugin in your IDE (IntelliJ IDEA, Eclipse, VS Code) for proper code completion and navigation.
@Data Annotation
The @Data annotation is a convenient shortcut that bundles several annotations together:
@Data
public class Person {
private String name;
private Integer age;
}
This single annotation generates:
@Getterfor all fields@Setterfor all fields@ToString@EqualsAndHashCode@RequiredArgsConstructor
Generated Code
public class Person {
private String name;
private Integer age;
public Person() {}
public String getName() { return this.name; }
public Integer getAge() { return this.age; }
public void setName(String name) { this.name = name; }
public void setAge(Integer age) { this.age = age; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Person)) return false;
Person other = (Person) o;
return Objects.equals(name, other.name)
&& Objects.equals(age, other.age);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Person(name=" + name + ", age=" + age + ")";
}
}
Individual Annotations
You can use these annotations separately for more control:
| Annotation | Purpose |
|---|---|
@Getter | Generates getter methods |
@Setter | Generates setter methods |
@ToString | Generates toString() method |
@EqualsAndHashCode | Generates equals() and hashCode() |
@NoArgsConstructor | Generates no-argument constructor |
@AllArgsConstructor | Generates all-args constructor |
Field-Level Usage
public class User {
@Getter @Setter
private String username;
@Getter // Read-only field
private final LocalDateTime createdAt = LocalDateTime.now();
}
@Builder Annotation
The @Builder annotation implements the Builder pattern - extremely useful for creating objects with many fields:
@Data
@Builder
public class Person {
private String name;
private Integer age;
private String email;
private String address;
}
Usage
Person person = Person.builder()
.name("TechyOwls")
.age(25)
.email("hello@techyowls.io")
.address("San Francisco")
.build();
Generated Builder Class
public class Person {
// fields...
public static PersonBuilder builder() {
return new PersonBuilder();
}
public static class PersonBuilder {
private String name;
private Integer age;
private String email;
private String address;
public PersonBuilder name(String name) {
this.name = name;
return this;
}
public PersonBuilder age(Integer age) {
this.age = age;
return this;
}
// ... other builder methods
public Person build() {
return new Person(name, age, email, address);
}
}
}
Builder with Default Values
@Builder
public class HttpRequest {
@Builder.Default
private String method = "GET";
@Builder.Default
private int timeout = 5000;
private String url;
}
// Usage
HttpRequest request = HttpRequest.builder()
.url("https://api.example.com")
.build(); // method="GET", timeout=5000
@RequiredArgsConstructor
This annotation generates a constructor with required arguments (final fields and fields with constraints):
The Problem: Constructor Injection Boilerplate
@Service
public class UserService {
private final UserRepository userRepository;
private final EmailService emailService;
private final AuditLogger auditLogger;
private final NotificationService notificationService;
private final CacheManager cacheManager;
// Verbose constructor!
@Autowired
public UserService(UserRepository userRepository,
EmailService emailService,
AuditLogger auditLogger,
NotificationService notificationService,
CacheManager cacheManager) {
this.userRepository = userRepository;
this.emailService = emailService;
this.auditLogger = auditLogger;
this.notificationService = notificationService;
this.cacheManager = cacheManager;
}
}
The Solution
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
private final EmailService emailService;
private final AuditLogger auditLogger;
private final NotificationService notificationService;
private final CacheManager cacheManager;
}
That’s it! Lombok generates the constructor automatically. Since Spring 4.3+, @Autowired is optional for single-constructor classes.
Other Useful Annotations
@Slf4j - Logging Made Easy
@Slf4j
@Service
public class PaymentService {
public void processPayment(Payment payment) {
log.info("Processing payment: {}", payment.getId());
// ... business logic
log.debug("Payment processed successfully");
}
}
Generates: private static final Logger log = LoggerFactory.getLogger(PaymentService.class);
@Value - Immutable Classes
@Value
public class Money {
BigDecimal amount;
String currency;
}
Creates an immutable class with:
- All fields are
private final - All-args constructor
- Getters (no setters)
equals(),hashCode(),toString()
@SneakyThrows - Checked Exception Handling
@SneakyThrows
public String readFile(String path) {
return Files.readString(Path.of(path));
// IOException is thrown sneakily!
}
Use with caution - bypasses checked exception handling.
Best Practices
- Use @Data for DTOs and entities - Perfect for simple data classes
- Prefer @Value for immutable objects - Thread-safe by design
- Use @Builder for complex objects - Especially with many optional fields
- Combine @RequiredArgsConstructor with final fields - Clean dependency injection
- Enable annotation processing in your IDE - For proper code navigation
Conclusion
Lombok significantly reduces boilerplate code in Java projects:
| Before | After |
|---|---|
| 50+ lines for a simple POJO | 5 lines with @Data |
| Manual constructor injection | @RequiredArgsConstructor |
| Builder pattern implementation | @Builder |
The library is widely adopted in the Java ecosystem and works seamlessly with Spring Boot, JPA, and other frameworks.
Key Takeaways:
- Use
@Datafor most POJOs - Use
@Builderfor objects with many fields - Use
@RequiredArgsConstructorfor dependency injection - Install the IDE plugin for the best experience
Advertisement
Moshiour Rahman
Software Architect & AI Engineer
Enterprise software architect with deep expertise in financial systems, distributed architecture, and AI-powered applications. Building large-scale systems at Fortune 500 companies. Specializing in LLM orchestration, multi-agent systems, and cloud-native solutions. I share battle-tested patterns from real enterprise projects.
Related Articles
Understanding Final Classes in Java
Learn when and why to use final classes in Java. Understand the design principle of prohibiting inheritance for better code safety.
JavaSingleton Design Pattern in Java: Part 2
Advanced singleton implementations including Double-Checked Locking with volatile and the Bill Pugh pattern using static inner classes.
JavaSingleton Design Pattern in Java: Part 1
Deep dive into the Singleton design pattern in Java. Learn different implementation approaches including eager initialization and lazy loading.
Comments
Comments are powered by GitHub Discussions.
Configure Giscus at giscus.app to enable comments.