Published on Sun Dec 15 2024 14:25:00 GMT+0000 (Coordinated Universal Time) by Purusothaman Ramanujam
Introduction - Reducing Boilerplate Code with Lombok
If you’ve been writing Java for any amount of time, you’ve probably noticed that a significant portion of your code consists of repetitive boilerplate - getters, setters, constructors, toString methods, and more. This boilerplate code is necessary but tedious to write and maintain. Enter Lombok, a Java library that automatically generates this boilerplate code at compile time using annotations.
In this blog post, we’ll explore how Lombok can dramatically reduce the amount of code you need to write while maintaining the same functionality.
What is Lombok?
Lombok is a Java library that uses annotations to automatically generate boilerplate code during compilation. It helps you write cleaner, more concise Java code by eliminating repetitive patterns like:
- Getters and setters
- Constructors
- toString() methods
- equals() and hashCode() methods
- Builder patterns
- Logging declarations
Adding Lombok to Your Project
Maven Dependency
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.30</version> <scope>provided</scope></dependency>
Gradle Dependency
compileOnly 'org.projectlombok:lombok:1.18.30'annotationProcessor 'org.projectlombok:lombok:1.18.30'
Important: You’ll also need to install the Lombok plugin in your IDE (IntelliJ IDEA, Eclipse, VS Code) for proper support.
Basic Annotations
@Getter and @Setter
Instead of writing getters and setters manually:
// Without Lombokpublic class Person { private String name; private int age; private String email;
public String getName() { return name; } public void setName(String name) { this.name = name; }
public int getAge() { return age; } public void setAge(int age) { this.age = age; }
public String getEmail() { return email; } public void setEmail(String email) { this.email = email; }}
With Lombok, you can simply write:
import lombok.Getter;import lombok.Setter;
@Getter@Setterpublic class Person { private String name; private int age; private String email;}
You can also apply these annotations to individual fields:
public class User { @Getter @Setter private String username;
@Getter private String password; // Only getter, no setter
private String email; // No getter/setter}
@ToString
Automatically generates a toString() method:
import lombok.ToString;
@ToStringpublic class Product { private String name; private double price; private String category;
// Lombok generates: // public String toString() { // return "Product(name=" + name + ", price=" + price + ", category=" + category + ")"; // }}
You can exclude specific fields:
@ToString(exclude = {"password", "secretKey"})public class User { private String username; private String password; private String secretKey; private String email;}
@EqualsAndHashCode
Generates equals() and hashCode() methods:
import lombok.EqualsAndHashCode;
@EqualsAndHashCodepublic class Employee { private String id; private String name; private String department;
// Lombok generates equals() and hashCode() based on all fields}
You can specify which fields to include:
@EqualsAndHashCode(onlyExplicitlyIncluded = true)public class Employee { @EqualsAndHashCode.Include private String id;
@EqualsAndHashCode.Include private String name;
private String department; // Not included in equals/hashCode}
@NoArgsConstructor, @RequiredArgsConstructor, @AllArgsConstructor
Generate different types of constructors:
import lombok.NoArgsConstructor;import lombok.RequiredArgsConstructor;import lombok.AllArgsConstructor;
@NoArgsConstructor@RequiredArgsConstructor@AllArgsConstructorpublic class Student { private String id; private final String name; // Required field private String email; private int age;}
This generates:
Student()
- no-args constructorStudent(String name)
- constructor with required fields (final fields)Student(String id, String name, String email, int age)
- constructor with all fields
Advanced Annotations
@Data
The @Data
annotation is a convenient shortcut that bundles the features of @ToString
, @EqualsAndHashCode
, @Getter
for all fields, @Setter
for all non-final fields, and @RequiredArgsConstructor
:
import lombok.Data;
@Datapublic class Book { private String isbn; private String title; private String author; private final int publicationYear; private double price;}
This single annotation generates:
- Getters for all fields
- Setters for all non-final fields
- toString() method
- equals() and hashCode() methods
- Constructor with required arguments (publicationYear)
@Builder
Generates a builder pattern for your class:
import lombok.Builder;import lombok.Data;
@Data@Builderpublic class Configuration { private String host; private int port; private String username; private String password; private boolean ssl;}
Now you can use the builder pattern:
Configuration config = Configuration.builder() .host("localhost") .port(8080) .username("admin") .password("secret") .ssl(true) .build();
@Slf4j
Automatically creates a logger field:
import lombok.extern.slf4j.Slf4j;
@Slf4jpublic class UserService { public void createUser(String username) { log.info("Creating user: {}", username); // ... implementation log.debug("User created successfully"); }}
This is equivalent to:
import org.slf4j.Logger;import org.slf4j.LoggerFactory;
public class UserService { private static final Logger log = LoggerFactory.getLogger(UserService.class); // ... rest of the class}
Practical Example
Let’s see how Lombok transforms a typical entity class:
Before Lombok (Traditional Java)
public class Customer { private String id; private String name; private String email; private String phone; private LocalDate birthDate;
public Customer() {}
public Customer(String id, String name, String email) { this.id = id; this.name = name; this.email = email; }
public String getId() { return id; } public void setId(String id) { this.id = id; }
public String getName() { return name; } public void setName(String name) { this.name = name; }
public String getEmail() { return email; } public void setEmail(String email) { this.email = email; }
public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; }
public LocalDate getBirthDate() { return birthDate; } public void setBirthDate(LocalDate birthDate) { this.birthDate = birthDate; }
@Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Customer customer = (Customer) o; return Objects.equals(id, customer.id) && Objects.equals(name, customer.name) && Objects.equals(email, customer.email); }
@Override public int hashCode() { return Objects.hash(id, name, email); }
@Override public String toString() { return "Customer{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", email='" + email + '\'' + ", phone='" + phone + '\'' + ", birthDate=" + birthDate + '}'; }}
After Lombok
import lombok.Data;import lombok.Builder;import lombok.NoArgsConstructor;import lombok.AllArgsConstructor;
@Data@Builder@NoArgsConstructor@AllArgsConstructorpublic class Customer { private String id; private String name; private String email; private String phone; private LocalDate birthDate;}
That’s it! Lombok generates all the boilerplate code automatically. The class is now much more readable and maintainable.
Best Practices
-
Use @Data Sparingly: While convenient,
@Data
can be overkill for simple classes. Consider using specific annotations instead. -
Be Careful with @EqualsAndHashCode: By default, it includes all fields, which might not be what you want for entities with generated IDs.
-
Consider Immutability: Use
@Value
for immutable objects instead of@Data
. -
IDE Support: Always install the Lombok plugin for your IDE to avoid compilation issues.
-
Documentation: Make sure your team understands Lombok annotations to maintain code readability.
Common Pitfalls
-
IDE Issues: Without proper IDE support, you might see compilation errors even though the code is correct.
-
Debugging: Generated code can make debugging slightly more complex, but modern IDEs handle this well.
-
Overuse: Don’t use Lombok annotations just because you can. Sometimes explicit code is more readable.
Conclusion
Lombok is a powerful tool that can significantly reduce boilerplate code in your Java projects. By using annotations, you can focus on the business logic rather than writing repetitive getters, setters, and other boilerplate methods.
Start with simple annotations like @Getter
and @Setter
, then gradually explore more advanced features like @Builder
and @Slf4j
. Remember that while Lombok makes your code more concise, it’s important to use it judiciously and ensure your team is comfortable with the annotations you choose.
In future posts, we’ll explore more advanced Lombok features and integration with frameworks like Spring Boot.
Written by Purusothaman Ramanujam
← Back to blog