Loading Now

Java: Project Lombok

lombok

Java: Project Lombok

What it is, why it helps, and how to use its most useful annotations in everyday Java coding. It’s designed to be copy-paste ready with examples you can drop into your projects.

What is Lombok and Why Use It?

Project Lombok is a Java library that reduces boilerplate code by generating common methods (getters, setters, constructors, builders, logging, etc.) at compile time. It makes code cleaner, more readable, and faster to write, especially in domain-driven and Spring-based applications.

Setup (Maven/Gradle + IDE)

Maven

<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <version>1.18.34</version> <!-- use latest stable -->
  <scope>provided</scope>
</dependency>

Gradle

dependencies {
  compileOnly 'org.projectlombok:lombok:1.18.34'
  annotationProcessor 'org.projectlombok:lombok:1.18.34'
  testCompileOnly 'org.projectlombok:lombok:1.18.34'
  testAnnotationProcessor 'org.projectlombok:lombok:1.18.34'
}

IDE

  • Install Lombok plugin (IntelliJ IDEA, Eclipse, VS Code).
  • Ensure annotation processing is enabled.

Core POJO Annotations

1) @Getter / @Setter

Generates getters and/or setters for fields.

import lombok.Getter;
import lombok.Setter;

public class Customer {
  @Getter @Setter
  private String name;

  @Getter
  private final String id; // no setter generated for final

  public Customer(String id) { this.id = id; }
}

Tips

  • Prefer immutable fields; only add setters where necessary.
  • You can control access level: @Setter(AccessLevel.PROTECTED).

2) @ToString / @EqualsAndHashCode

Generates readable toString() and consistent equality/hash methods.

import lombok.ToString;
import lombok.EqualsAndHashCode;

@ToString(exclude = "password")
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class User {
  @EqualsAndHashCode.Include
  private String username;

  private String password;
}

Tips

  • Use exclude for sensitive fields.
  • For collections, be aware of performance in large graphs.

3) @Data

All-in-one: @Getter, @Setter, @ToString, @EqualsAndHashCode, and required constructor.

import lombok.Data;

@Data
public class Product {
  private String sku;
  private String name;
  private double price;
}

Tips

  • Great for simple mutable DTOs.
  • Avoid @Data on JPA entities with complex equals/hash semantics; use explicit annotations.

4) @Value

Immutable variant of @Data: all fields private final, no setters, all-args constructor.

import lombok.Value;

@Value
public class Money {
  String currency;
  long minorUnits;
}

Tips

  • Perfect for value objects.
  • Combine with @Builder for readable construction.

Constructors

5) @NoArgsConstructor, @AllArgsConstructor, @RequiredArgsConstructor

Generate constructors with different field inclusion rules.

import lombok.*;

@RequiredArgsConstructor
@NoArgsConstructor(force = true) // initializes final fields with defaults
@AllArgsConstructor
public class Account {
  @NonNull private String id;  // included by RequiredArgsConstructor
  private String owner;
}

Tips

  • @RequiredArgsConstructor includes final and @NonNull fields.
  • force = true is useful for frameworks requiring no-args constructors.

Builders for Readable Object Creation

6) @Builder

Generates a fluent builder API, optional toBuilder for cloning.

import lombok.Builder;
import lombok.Data;

@Data
@Builder(toBuilder = true)
public class Email {
  private String to;
  private String subject;
  private String body;
  @Builder.Default
  private boolean html = true;
}

// Usage
var email = Email.builder()
  .to("ashish@example.com")
  .subject("Hello")
  .body("Welcome!")
  .build();

var updated = email.toBuilder().subject("Updated").build();

Tips

  • Use @Builder.Default to keep default values when field isn’t set by builder.
  • On inheritance, prefer @SuperBuilder.

7) @SuperBuilder

Builder support with inheritance.

import lombok.*;
import lombok.experimental.SuperBuilder;

@SuperBuilder
@Data
class BaseRequest {
  private String requestId;
}

@SuperBuilder
@Data
class PaymentRequest extends BaseRequest {
  private long amount;
}

// Usage
var req = PaymentRequest.builder()
  .requestId("REQ-1")
  .amount(5000L)
  .build();

8) @Singular

Builder support for collections with single-item methods.

import lombok.Builder;
import lombok.Singular;
import java.util.List;

@Builder
public class Query {
  @Singular private List<String> filters;
}

// Usage
var q = Query.builder()
  .filter("status=ACTIVE")
  .filter("country=IN")
  .build();

Field and Access Customization

9) @Accessors

Customize naming conventions and fluent/chain access.

import lombok.Data;
import lombok.experimental.Accessors;

@Data
@Accessors(fluent = true, chain = true)
public class Config {
  private boolean debug;
  private String region;
}

// Usage
new Config().debug(true).region("ap-south-1");

10) @FieldDefaults

Set default field modifiers across class.

import lombok.experimental.FieldDefaults;
import lombok.AccessLevel;

@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
public class Settings {
  String env;
  boolean featureX;
}

11) @With

Creates a copy with a different value for a field (immutability-friendly).

import lombok.With;
import lombok.Value;

@Value
public class Address {
  @With String city;
  String country;
}

// Usage
var a1 = new Address("Gurgaon", "India");
var a2 = a1.withCity("Bengaluru");

Null-safety and Resource Handling

12) @NonNull

Generates a null-check at the start of method/constructor.

import lombok.NonNull;

public void send(@NonNull String recipient) {
  // throws NullPointerException if recipient is null
}

13) @Cleanup

Auto-closes resources at the end of scope.

import lombok.Cleanup;
import java.io.*;

void readFile() throws IOException {
  @Cleanup var in = new FileInputStream("data.txt");
  // use 'in'; it will be closed automatically
}

Concurrency and Exceptions

14) @Synchronized

Safer alternative to synchronized using private lock objects.

import lombok.Synchronized;

private final Object lock = new Object();

@Synchronized("lock")
public void increment() {
  // thread-safe block
}

15) @SneakyThrows

Allows throwing checked exceptions without declaring them. Use sparingly – it can hide important exception contracts.

import lombok.SneakyThrows;

@SneakyThrows
void run() {
  throw new IOException("oops");
}

Utility and Patterns

16) @UtilityClass

Creates a final class with private constructor; members become static.

import lombok.experimental.UtilityClass;

@UtilityClass
public class Strings {
  public String trimOrEmpty(String s) { return s == null ? "" : s.trim(); }
}

// Usage
Strings.trimOrEmpty(null);

17) Logging: @Slf4j, @Log4j2, @Log, etc.

Injects a static final logger field. Variants: @Log, @Log4j2, @CommonsLog, @JBossLog, etc., depending on your logging framework.

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class PaymentService {
  public void charge(long amount) {
    log.info("Charging {}", amount);
  }
}

Serialization & Framework Interop

18) @Jacksonized (with @Builder)

Ensures Jackson can deserialize Lombok-built classes.

import lombok.Builder;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;

@Value
@Builder
@Jacksonized
public class Person {
  String name;
  int age;
}

Tip: Add jackson-databind dependency; works seamlessly for JSON (e.g., in Spring Boot).

Best Practices for Day-to-Day Coding

  1. Prefer immutability: Use @Value, @With, and builders for thread-safe, readable code.
  2. Be explicit with equals/hash: For entities, consider @EqualsAndHashCode(onlyExplicitlyIncluded = true).
  3. Guard sensitive fields: Exclude tokens/passwords from @ToString.
  4. Use @Builder.Default wisely: Define deterministic defaults in builders.
  5. Log responsibly: @Slf4j is convenient—log at appropriate levels and avoid noisy debug logs in production.
  6. Don’t hide exceptions: Use @SneakyThrows only when a method truly can’t or shouldn’t declare checked exceptions.
  7. Be careful with @Data on entities: It can generate setters that break invariants; consider @Getter + explicit constructors.
  8. Enable delombok in CI (optional): Generate plain Java for tools that need it.

Common Pitfalls & How to Avoid Them

  • IDE not showing generated methods: Enable annotation processing and install the plugin.
  • JPA entities: Avoid @EqualsAndHashCode on fields that change; consider using IDs only.
  • Serialization issues with builders: Add @Jacksonized for seamless Jackson interop.
  • Inheritance + Builder: Use @SuperBuilder, not @Builder, to avoid missing base fields.

Cheat Sheet (When to Use What)

  • DTOs@Data
  • Value objects@Value, @With, @Builder
  • Inheritance@SuperBuilder
  • Logging@Slf4j
  • Null-checks@NonNull
  • Resource management@Cleanup
  • Framework utilities@Jacksonized for JSON, @NoArgsConstructor for JPA

Bonus: Delombok (Generate Plain Java)

If your tooling requires plain Java (e.g., coverage tools), you can delombok during build:

<plugin>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok-maven-plugin</artifactId>
  <version>1.18.20.0</version>
  <executions>
    <execution>
      <phase>generate-sources</phase>
      <goals><goal>delombok</goal></goals>
      <configuration>
        <sourceDirectory>${project.basedir}/src/main/java</sourceDirectory>
        <outputDirectory>${project.build.directory}/delombok</outputDirectory>
      </configuration>
    </execution>
  </executions>
</plugin>

Post Comment