Everything Java

← Back to blog

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:

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 Lombok
public 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
@Setter
public 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;
@ToString
public 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;
@EqualsAndHashCode
public 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
@AllArgsConstructor
public class Student {
private String id;
private final String name; // Required field
private String email;
private int age;
}

This generates:

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;
@Data
public class Book {
private String isbn;
private String title;
private String author;
private final int publicationYear;
private double price;
}

This single annotation generates:

@Builder

Generates a builder pattern for your class:

import lombok.Builder;
import lombok.Data;
@Data
@Builder
public 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;
@Slf4j
public 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
@AllArgsConstructor
public 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

  1. Use @Data Sparingly: While convenient, @Data can be overkill for simple classes. Consider using specific annotations instead.

  2. Be Careful with @EqualsAndHashCode: By default, it includes all fields, which might not be what you want for entities with generated IDs.

  3. Consider Immutability: Use @Value for immutable objects instead of @Data.

  4. IDE Support: Always install the Lombok plugin for your IDE to avoid compilation issues.

  5. Documentation: Make sure your team understands Lombok annotations to maintain code readability.

Common Pitfalls

  1. IDE Issues: Without proper IDE support, you might see compilation errors even though the code is correct.

  2. Debugging: Generated code can make debugging slightly more complex, but modern IDEs handle this well.

  3. 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