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:
@Log→ java.util.logging@Log4j2→ Log4j 2