Lombok is a compile-time annotation processor that modifies the Abstract Syntax Tree (AST) to reduce boilerplate code in Java. It integrates into javac using the JSR 269 Pluggable Annotation Processing API and injects methods into compiled classes without altering the source code. This means that Lombok operates before the Java compiler generates bytecode but after it has parsed the source.

The primary advantage of Lombok is its ability to automate repetitive method generation, such as getters, setters, constructors, and logging fields, which reduces manual errors and improves maintainability. However, it introduces potential debugging challenges because the generated methods are not visible in source code, requiring IDE integration.

Class-Level Annotations

Several Lombok annotations apply to the entire class, modifying its behavior by injecting multiple methods or altering field visibility.

@Data

This annotation bundles multiple Lombok features into one: @Getter, @Setter, @ToString, @EqualsAndHashCode, and @RequiredArgsConstructor. It is ideal for mutable data classes.

import lombok.Data;
 
@Data
class User {
    private String username;
    private int age;
}

This results in:

  • getUsername(), setUsername(), getAge(), setAge()
  • toString(), equals(), hashCode()
  • A constructor with all final fields

@Value

This is the immutable counterpart of @Data. It marks all fields as private final, removes setters, and ensures the class itself is final.

import lombok.Value;
 
@Value
class ImmutableUser {
    String username;
    int age;
}

The compiler prevents field modification after initialization, ensuring thread safety without explicit defensive copying.

@Builder

Implements the Builder pattern by providing a fluent API for object creation.

import lombok.Builder;
 
@Builder
class Car {
    private String model;
    private int year;
}

Generates a static builder method:

Car.builder().model("Tesla").year(2023).build();

If used on a method, it converts it into a factory method.

Field-Level Annotations

These annotations apply only to specific fields, rather than the entire class.

@Getter and @Setter

Generate getter and setter methods only for annotated fields.

import lombok.Getter;
 
class Account {
    @Getter private String id;
}

This generates:

public String getId() { return id; }

@NonNull

Adds null-checking logic to constructors and setters, preventing null assignments.

import lombok.NonNull;
 
class User {
    private @NonNull String username;
}

Generates:

public void setUsername(@NonNull String username) {
    if (username == null) throw new NullPointerException("username is marked non-null but is null");
}

@With

Creates an immutable copying method that returns a new instance with only one field changed.

import lombok.With;
 
class Config {
    @With private final String apiKey;
}

This generates:

public Config withApiKey(String apiKey) {
    return new Config(apiKey);
}

Method-Level Annotations

Lombok also provides annotations that modify individual methods.

@ToString

Generates a custom toString() implementation. By default, includes all fields.

import lombok.ToString;
 
@ToString
class Product {
    private String name;
    private double price;
}

Generates:

public String toString() {
    return "Product(name=" + this.name + ", price=" + this.price + ")";
}

Fields can be excluded: @ToString(exclude = "price")

@EqualsAndHashCode

Generates equals() and hashCode() based on field values.

import lombok.EqualsAndHashCode;
 
@EqualsAndHashCode
class Employee {
    private String name;
}

Ensures object equality is based on field values rather than reference identity.

@Synchronized

Provides a thread-safe alternative to synchronized methods by creating a private lock object.

import lombok.Synchronized;
 
class Counter {
    @Synchronized
    public void increment() { ... }
}

This generates:

private final Object $lock = new Object();
public void increment() {
    synchronized(this.$lock) { ... }
}

Using an explicit lock object instead of synchronized methods prevents deadlocks in subclassing.

@SneakyThrows

Suppresses checked exceptions without requiring a throws clause.

import lombok.SneakyThrows;
 
class Example {
    @SneakyThrows
    public void riskyMethod() {
        throw new Exception("Oops");
    }
}

This eliminates explicit try-catch blocks in places where exception handling is unnecessary.

Logging Annotations

Lombok can automatically inject logger instances.

import lombok.extern.slf4j.Slf4j;
 
@Slf4j
class LoggerExample {
    public void logSomething() {
        log.info("Logging message");
    }
}

This eliminates the need for manually defining:

private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LoggerExample.class);

Alternative logging frameworks:

  • @Logjava.util.logging
  • @Log4j2Log4j 2