Skip to main content

Chapter 4: Lombok - Reduce Boilerplate Code

Introduction​

Question: How many lines of code for a simple Product class?

Without Lombok: ~100 lines (getters, setters, constructors, toString, equals, hashCode)

With Lombok: ~10 lines

Lombok = Productivity 🚀


What is Lombok?​

Definition​

Lombok is a Java library that automatically generates repetitive code (boilerplate) using annotations.

Boilerplate = Repetitive and boring code 😴

Examples:

  • Getters and setters
  • Constructors
  • toString()
  • equals() and hashCode()

Analogy​

Without Lombok:

You are a chef who must:

  1. Wash the vegetables
  2. Cut the vegetables
  3. Cook the dish
  4. Clean the kitchen

With Lombok:

You say "Prepare a dish for me" and an assistant does 1, 2, and 4 automatically. You focus on 3 (the cooking).


Installing Lombok​

Step 1: Add the Maven Dependency​

File: pom.xml

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>

Note: <optional>true</optional> indicates that Lombok is only used at compile time.


Step 2: Install the IDE Plugin​

VS Code​

  1. Open Extensions (Ctrl+Shift+X or Cmd+Shift+X)
  2. Search for: "Lombok Annotations Support for VS Code"
  3. Install the extension by Gabriel Basilio Brito
  4. Restart VS Code

IntelliJ IDEA​

  1. Go to File → Settings → Plugins
  2. Search for: "Lombok"
  3. Install the plugin
  4. Restart IntelliJ

Eclipse​

  1. Download lombok.jar from projectlombok.org
  2. Run: java -jar lombok.jar
  3. Select your Eclipse installation
  4. Restart Eclipse

Step 3: Verification​

Create a test class:

package com.example.demo.model;

import lombok.Data;

@Data
public class TestLombok {
private String name;
private Integer age;
}

Compile:

mvn clean compile

Check in the console:

[INFO] BUILD SUCCESS

✅ Lombok works!


Main Annotations​

1. @Data​

Automatically generates:

  • All getters
  • All setters
  • toString()
  • equals() and hashCode()
  • Constructor for final fields

Before Lombok​

File: model/Product.java

package com.example.demo.model;

import jakarta.persistence.*;

@Entity
@Table(name = "products")
public class Product {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String name;
private Double price;

// Empty constructor
public Product() {}

// Constructor with parameters
public Product(String name, Double price) {
this.name = name;
this.price = price;
}

// Getters
public Long getId() {
return id;
}

public String getName() {
return name;
}

public Double getPrice() {
return price;
}

// Setters
public void setId(Long id) {
this.id = id;
}

public void setName(String name) {
this.name = name;
}

public void setPrice(Double price) {
this.price = price;
}

// toString()
@Override
public String toString() {
return "Product{" +
"id=" + id +
", name='" + name + '\'' +
", price=" + price +
'}';
}

// equals() and hashCode() (50+ more lines)
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Product product = (Product) o;
return Objects.equals(id, product.id) &&
Objects.equals(name, product.name) &&
Objects.equals(price, product.price);
}

@Override
public int hashCode() {
return Objects.hash(id, name, price);
}
}

Total: ~100 lines


After Lombok​

File: model/Product.java

package com.example.demo.model;

import jakarta.persistence.*;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;

@Entity
@Table(name = "products")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String name;
private Double price;
}

Total: ~15 lines

Reduction: From 100 lines to 15 lines! (85% reduction) 🎉


2. @Getter / @Setter​

Generates getters/setters individually.

Example:

package com.example.demo.model;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

@Entity
public class Product {

@Id
@GeneratedValue
private Long id;

@Getter @Setter
private String name;

@Getter @Setter
private Double price;
}

Equivalent to:

public String getName() { return name; }
public void setName(String name) { this.name = name; }

public Double getPrice() { return price; }
public void setPrice(Double price) { this.price = price; }

Use at class level:

@Getter
@Setter
@Entity
public class Product {
private Long id;
private String name;
private Double price;
}

Generates all getters and setters for all fields.


3. @NoArgsConstructor / @AllArgsConstructor​

@NoArgsConstructor​

Generates a no-arguments constructor.

@NoArgsConstructor
@Entity
public class Product {
private Long id;
private String name;
private Double price;
}

Equivalent to:

public Product() {}

@AllArgsConstructor​

Generates a constructor with all arguments.

@AllArgsConstructor
@Entity
public class Product {
private Long id;
private String name;
private Double price;
}

Equivalent to:

public Product(Long id, String name, Double price) {
this.id = id;
this.name = name;
this.price = price;
}

Common combination:

@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Product {
private Long id;
private String name;
private Double price;
}

Generates:

  • Product() - Empty constructor
  • Product(Long id, String name, Double price) - Full constructor

4. @RequiredArgsConstructor​

Generates a constructor with final or @NonNull fields.

Example:

@RequiredArgsConstructor
@Entity
public class Product {
private Long id;

@NonNull
private String name;

@NonNull
private Double price;
}

Generates:

public Product(String name, Double price) {
this.name = name;
this.price = price;
}

Note: id is excluded because it is not final and not @NonNull.


5. @Builder​

Builder pattern for object creation.

Example:

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Product {
@Id
@GeneratedValue
private Long id;
private String name;
private Double price;
}

Usage:

Product product = Product.builder()
.name("Laptop")
.price(999.99)
.build();

Advantages:

  • Readable code
  • Named parameters
  • Flexible order

6. @ToString​

Automatically generates toString().

Example:

@ToString
@Entity
public class Product {
private Long id;
private String name;
private Double price;
}

Equivalent to:

@Override
public String toString() {
return "Product(id=" + id + ", name=" + name + ", price=" + price + ")";
}

Exclude fields:

@ToString(exclude = {"price"})
@Entity
public class Product {
private Long id;
private String name;
private Double price;
}

Result:

Product(id=1, name=Laptop)

7. @EqualsAndHashCode​

Generates equals() and hashCode().

Example:

@EqualsAndHashCode
@Entity
public class Product {
private Long id;
private String name;
private Double price;
}

Equivalent to:

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Product product = (Product) o;
return Objects.equals(id, product.id) &&
Objects.equals(name, product.name) &&
Objects.equals(price, product.price);
}

@Override
public int hashCode() {
return Objects.hash(id, name, price);
}

Use only ID:

@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@Entity
public class Product {
@EqualsAndHashCode.Include
@Id
private Long id;

private String name;
private Double price;
}

✅ Best practice for JPA entities!


Common Combinations​

For JPA Entities​

Recommended configuration:

@Entity
@Table(name = "products")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@NotBlank
private String name;

@Min(0)
private Double price;
}

Generates:

  • Getters and setters
  • Empty constructor (required by JPA)
  • Full constructor
  • toString(), equals(), hashCode()

For DTOs​

Recommended configuration:

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ProductDTO {
private Long id;
private String name;
private Double price;
}

Usage:

ProductDTO dto = ProductDTO.builder()
.id(1L)
.name("Laptop")
.price(999.99)
.build();

For Services (Dependency Injection)​

Recommended configuration:

@Service
@RequiredArgsConstructor
public class ProductService {

private final ProductRepository productRepository;

// No need for a constructor!

public List<Product> findAll() {
return productRepository.findAll();
}
}

Lombok automatically generates:

public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}

Advantages and Disadvantages​

Advantages​

✅ Less boilerplate code (85% reduction)
✅ More readable code (focus on business logic)
✅ Easier maintenance (no getters/setters to update)
✅ Increased productivity (faster development)
✅ Fewer errors (automatically generated code)


Disadvantages​

❌ External dependency (adding a library)
❌ Requires IDE plugin (for code completion)
❌ Can hide complexity (invisible code)
❌ Learning curve (for beginners)
❌ More complex debugging (generated code not visible)


Best Practices​

1. Avoid @Data for Entities with Relationships​

Problem:

@Entity
@Data // WARNING!
public class Order {
@Id
private Long id;

@OneToMany
private List<OrderItem> items;
}

Risk: toString() can cause infinite loops with bidirectional relationships.

Solution:

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString(exclude = {"items"})
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Order {
@EqualsAndHashCode.Include
@Id
private Long id;

@OneToMany
private List<OrderItem> items;
}

2. Use @RequiredArgsConstructor for Services​

Good:

@Service
@RequiredArgsConstructor
public class ProductService {
private final ProductRepository productRepository;
}

Bad:

@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
}

Why? Constructor injection is preferred (testability, immutability).


3. Always Combine with @NoArgsConstructor for JPA​

JPA requires an empty constructor:

@Entity
@Data
@NoArgsConstructor // REQUIRED for JPA
@AllArgsConstructor
public class Product {
@Id
private Long id;
private String name;
}

Summary​

Essential Annotations​

AnnotationGenerates
@DataGetters, Setters, toString, equals, hashCode
@Getter / @SetterGetters or Setters
@NoArgsConstructorEmpty constructor
@AllArgsConstructorFull constructor
@RequiredArgsConstructorConstructor with final/@NonNull
@BuilderBuilder pattern
@ToStringtoString()
@EqualsAndHashCodeequals() and hashCode()

Standard JPA Configuration​

@Entity
@Table(name = "products")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String name;
private Double price;
}

✅ Minimal code, maximum functionality!


Next Step​

You now have all the theoretical tools from Module 4! Move on to TP4.1 to migrate your application to PostgreSQL with Lombok.

➡️ TP4.1: PostgreSQL Migration

⬅️ Previous Chapter: Hibernate DDL

⬅️ Back to Module 4