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
excludefor 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
@Dataon 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
@Builderfor 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
@RequiredArgsConstructorincludesfinaland@NonNullfields.force = trueis 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.Defaultto 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
- Prefer immutability: Use
@Value,@With, and builders for thread-safe, readable code. - Be explicit with equals/hash: For entities, consider
@EqualsAndHashCode(onlyExplicitlyIncluded = true). - Guard sensitive fields: Exclude tokens/passwords from
@ToString. - Use
@Builder.Defaultwisely: Define deterministic defaults in builders. - Log responsibly:
@Slf4jis convenient—log at appropriate levels and avoid noisy debug logs in production. - Don’t hide exceptions: Use
@SneakyThrowsonly when a method truly can’t or shouldn’t declare checked exceptions. - Be careful with
@Dataon entities: It can generate setters that break invariants; consider@Getter+ explicit constructors. - 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
@EqualsAndHashCodeon fields that change; consider using IDs only. - Serialization issues with builders: Add
@Jacksonizedfor 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 →
@Jacksonizedfor JSON,@NoArgsConstructorfor 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