00:00

Spring Boot with Lombok

Reading time: 16 min read
Difficulty level: Beginner to Intermediate

Java classes often contain more repetitive code than real logic: getters, setters, constructors, toString(), equals() and hashCode(). Project Lombok is an open-source library that writes this code for you, based on a few annotations, so your classes stay short and focused.

This tutorial shows how to set up Lombok in a Spring Boot project, explains each important annotation with examples, shows how to use Lombok safely in each layer, especially with JPA entities, and compares it with Java records.

1. What Is Lombok?

Here is a simple class written in plain Java:

Product.java (without Lombok)
public class Product {

    private Long id;
    private String name;
    private BigDecimal price;

    public Product() {
    }

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

    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }

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

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

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

    // ... plus equals() and hashCode(), another 20 lines
}

And here is the same class with Lombok:

Product.java (with Lombok)
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {

    private Long id;
    private String name;
    private BigDecimal price;
}

Both versions behave the same. The Lombok version is easier to read, and when you add a field, the getter, setter, toString() and the other methods update automatically, so they can never be forgotten or fall out of date.

2. How Lombok Works

Lombok is an annotation processor. It runs inside the Java compiler:

  1. The compiler reads your source code and finds Lombok annotations such as @Getter.
  2. Lombok adds the matching methods to the class while it is being compiled.
  3. The compiler writes a normal .class file that contains those methods.

The result is ordinary Java bytecode, exactly as if you had written the methods by hand. That means:

  • No runtime cost: nothing extra runs while your application runs.
  • No runtime dependency: Lombok is only needed for compiling, not inside the final JAR.
  • Normal for other tools: Spring, Jackson and Hibernate simply see regular getters, setters and constructors.

3. Adding Lombok to a Spring Boot Project

The easiest way is to tick Lombok when creating the project on Spring Initializr. To add it to an existing Maven project, use three pieces. Spring Boot already manages the Lombok version, so you do not need to choose one:

pom.xml
<dependencies>
    <!-- 1. The library itself, only needed while compiling -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

<build>
    <plugins>
        <!-- 2. Register Lombok as an annotation processor for the compiler -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
                <annotationProcessorPaths>
                    <path>
                        <groupId>org.projectlombok</groupId>
                        <artifactId>lombok</artifactId>
                        <version>${lombok.version}</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>

        <!-- 3. Keep Lombok out of the packaged JAR -->
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <excludes>
                    <exclude>
                        <groupId>org.projectlombok</groupId>
                        <artifactId>lombok</artifactId>
                    </exclude>
                </excludes>
            </configuration>
        </plugin>
    </plugins>
</build>
PartWhy it is needed
<optional>true</optional>Stops Lombok from being passed on to other projects that depend on yours
annotationProcessorPathsTells the compiler to run Lombok; newer JDKs no longer find annotation processors automatically
Spring Boot plugin excludesLeaves Lombok out of the runnable JAR, because it is not needed at runtime

For Gradle, two lines are enough:

build.gradle
dependencies {
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
}

4. IDE Setup

Your build tool compiles Lombok code correctly on its own, but the IDE also needs to understand Lombok, or it shows false errors such as “method getName() not found”.

IDEWhat to do
IntelliJ IDEALombok support is built in. If prompted, enable Settings → Build, Execution, Deployment → Compiler → Annotation Processors → Enable annotation processing.
Eclipse / Spring ToolsRun the Lombok JAR once (java -jar lombok.jar), point it at your Eclipse installation, and restart Eclipse.
VS CodeThe Java extension pack includes Lombok support; no extra steps.

5. @Getter and @Setter

@Getter and @Setter generate accessor methods. Put them on the class for all fields, or on single fields for finer control:

Customer.java
@Getter
@Setter
public class Customer {

    private Long id;
    private String name;
    private boolean active;               // generates isActive() and setActive(...)

    @Setter(AccessLevel.NONE)             // no setter for this field
    private LocalDateTime createdAt = LocalDateTime.now();

    @Getter(AccessLevel.PROTECTED)        // getter only visible to subclasses
    private String internalNote;
}

Use AccessLevel to generate methods with other visibility, or AccessLevel.NONE to skip one. Only add setters where values really should change.

6. Constructor Annotations

AnnotationGenerates a constructor with...Typical use
@NoArgsConstructorNo parametersJPA entities and frameworks that create objects by reflection
@AllArgsConstructorOne parameter per fieldDTOs and test data
@RequiredArgsConstructorParameters for final and @NonNull fields onlySpring beans with constructor injection

@RequiredArgsConstructor is especially useful in Spring, because it gives you clean constructor injection:

OrderService.java
@Service
@RequiredArgsConstructor
public class OrderService {

    private final OrderRepository orderRepository;   // injected
    private final PaymentClient paymentClient;       // injected
    private int retryCount = 3;                      // not final: not in the constructor

    public Order place(OrderRequest request) {
        Order order = orderRepository.save(Order.from(request));
        paymentClient.charge(order);
        return order;
    }
}

Lombok generates a constructor taking OrderRepository and PaymentClient. Because it is the only constructor, Spring uses it automatically, with no @Autowired needed. Add a new final dependency, and the constructor updates itself.

7. @ToString and @EqualsAndHashCode

@ToString generates a readable toString(), which is handy in logs. Exclude fields that are sensitive or large:

Account.java
@Getter
@ToString
public class Account {

    private Long id;
    private String email;

    @ToString.Exclude
    private String passwordHash;     // never printed in logs
}

// account.toString() -> "Account(id=7, email=asha@example.com)"

@EqualsAndHashCode generates equals() and hashCode(). By default it uses all fields; it is usually better to choose them explicitly:

Product.java
@Getter
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Product {

    @EqualsAndHashCode.Include
    private String sku;              // two products are equal if their SKUs match

    private String name;
    private BigDecimal price;
}

8. @Data and @Value

Two shortcut annotations bundle several others:

@Data@Value
GettersYesYes
SettersYes, for non-final fieldsNo
FieldsAs you declare themAll made private final
ConstructorFor required (final) fieldsFor all fields
toString, equals, hashCodeYesYes
ClassCan be changed after creation (mutable)Cannot be changed (immutable), and is final
Examples
@Data
public class CartItemForm {          // mutable: a web form fills it field by field
    private String sku;
    private int quantity;
}

@Value
public class Money {                 // immutable: safe to share between threads
    BigDecimal amount;
    String currency;
}

Money price = new Money(new BigDecimal("19.99"), "INR");

Prefer @Value for small value objects that should never change. Use @Data for simple data holders such as form objects, but never for JPA entities (see section 13).

9. @Builder

@Builder creates a builder, a readable way to construct objects with many fields, where each value is named:

NotificationRequest.java
@Getter
@Builder(toBuilder = true)
public class NotificationRequest {

    private String recipient;
    private String subject;
    private String body;

    @Builder.Default
    private Channel channel = Channel.EMAIL;     // used when channel() is not called

    @Builder.Default
    private int priority = 5;
}
Usage
NotificationRequest welcome = NotificationRequest.builder()
        .recipient("asha@example.com")
        .subject("Welcome!")
        .body("Thanks for signing up.")
        .build();                                // channel = EMAIL, priority = 5

// toBuilder() copies an existing object and changes only some fields
NotificationRequest urgent = welcome.toBuilder()
        .channel(Channel.SMS)
        .priority(1)
        .build();
FeatureWhat it does
@Builder.DefaultKeeps a field’s default value when the builder does not set it; without it, the field ends up null or 0
toBuilder = trueAdds toBuilder(), which copies an object into a new builder
@SingularOn a collection field, adds a method that adds one item at a time, such as .tag("new")

10. @Slf4j and @NonNull

@Slf4j adds a ready-to-use logger field named log:

InvoiceService.java
@Slf4j
@Service
@RequiredArgsConstructor
public class InvoiceService {

    private final InvoiceRepository invoiceRepository;

    public Invoice create(Order order) {
        log.info("Creating invoice for order {}", order.getId());
        return invoiceRepository.save(Invoice.from(order));
    }
}

It replaces the usual line private static final Logger log = LoggerFactory.getLogger(InvoiceService.class);, and it can never point at the wrong class by mistake after copying code.

@NonNull adds a null check at the start of a method or constructor:

ShippingService.java
public Label createLabel(@NonNull Address address) {
    // Lombok inserts: if (address == null) throw new NullPointerException("address is marked non-null but is null");
    return labelPrinter.print(address);
}

Use it for internal safety checks. For validating user input in requests, use Bean Validation annotations such as @NotNull instead, which return friendly error responses.

11. Annotation Cheat Sheet

AnnotationGenerates
@Getter / @SetterGetter and setter methods
@NoArgsConstructorA constructor with no parameters
@AllArgsConstructorA constructor with all fields
@RequiredArgsConstructorA constructor with final and @NonNull fields
@ToStringA toString() method
@EqualsAndHashCodeequals() and hashCode()
@DataGetters, setters, required-args constructor, toString, equals, hashCode
@ValueAn immutable class: private final fields, getters, all-args constructor and more
@BuilderA builder for readable object creation
@WithMethods like withPrice(...) that return a copy with one field changed
@Slf4jA log field using SLF4J
@NonNullA null check on a parameter or field

12. Lombok in Each Application Layer

LayerRecommended Lombok annotationsAvoid
Controller@RequiredArgsConstructor, @Slf4jSetters on controllers
Service@RequiredArgsConstructor, @Slf4j@Data (services are not data)
Request/response DTO@Value or @Builder, or a Java recordMutable setters you do not need
Form or mutable DTO@Data, @NoArgsConstructor—
JPA entity@Getter, @Setter, @NoArgsConstructor@Data, default @ToString and @EqualsAndHashCode
ProductController.java
@Slf4j
@RestController
@RequestMapping("/api/products")
@RequiredArgsConstructor
public class ProductController {

    private final ProductService productService;

    @GetMapping("/{id}")
    public ProductResponse get(@PathVariable Long id) {
        log.debug("Fetching product {}", id);
        return productService.findById(id);
    }
}

13. Lombok and JPA Entities

JPA entities need special care, because Hibernate uses proxies and lazy loading. @Data on an entity causes three problems:

ProblemCauseResult
Unexpected database queriestoString() touches lazy relationshipsSlow logging, or a LazyInitializationException
Endless loopstoString() or hashCode() follows a two-way relationship back and forthStackOverflowError
Entities lost from setshashCode() uses fields that change, including the id assigned on saveAn entity added to a HashSet cannot be found again

A safe pattern uses only @Getter and @Setter, and writes equals() and hashCode() by hand:

Customer.java
@Entity
@Table(name = "customers")
@Getter
@Setter
@NoArgsConstructor
public class Customer {

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

    private String name;

    @OneToMany(mappedBy = "customer")
    private List<Order> orders = new ArrayList<>();

    @Override
    public boolean equals(Object other) {
        if (this == other) return true;
        if (!(other instanceof Customer that)) return false;
        return id != null && id.equals(that.getId());
    }

    @Override
    public int hashCode() {
        return getClass().hashCode();   // stays the same before and after saving
    }

    @Override
    public String toString() {
        return "Customer(id=" + id + ", name=" + name + ")";   // no relationships
    }
}

Entities are compared by their database id, the hash code never changes, and toString() never touches the lazy orders list. For more on entities, see the Spring Data JPA Overview.

14. Lombok vs Java Records

Since Java 16, records give you immutable data classes without any library:

ProductResponse.java
public record ProductResponse(Long id, String name, BigDecimal price) {
}
Java recordLombok
Needs a libraryNo, built into JavaYes
Immutable data carriers (DTOs)ExcellentGood (@Value)
Mutable classes with settersNot possibleYes
JPA entitiesNot suitableYes, with care
BuildersNot built in@Builder
Spring beans and loggingNot applicable@RequiredArgsConstructor, @Slf4j

A practical rule: use records for request and response DTOs, and Lombok for entities, Spring beans and classes that need builders or setters. Both work well side by side in the same project.

15. Configuring Lombok with lombok.config

A lombok.config file in the project root sets rules for every class below it. A useful starting point:

lombok.config
# This is the root configuration; do not look in parent folders
config.stopBubbling = true

# Mark generated code so test coverage tools such as JaCoCo ignore it
lombok.addLombokGeneratedAnnotation = true

# Copy @Qualifier and @Value from fields to the generated constructor
lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Qualifier
lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Value

# Prevent the risky @Data on any class, if your team agrees
# lombok.data.flagUsage = error
SettingBenefit
config.stopBubblingKeeps settings predictable when projects are nested
addLombokGeneratedAnnotationCoverage reports measure only the code you actually wrote
copyableAnnotationsMakes @Qualifier and @Value work with @RequiredArgsConstructor
*.flagUsageWarns about, or forbids, annotations your team has decided not to use

16. Seeing the Generated Code

Sometimes you want to see exactly what Lombok generated. Delombok converts Lombok annotations into the plain Java code they produce:

  • In IntelliJ IDEA: right-click the class and choose Refactor → Delombok. Undo it afterwards if you only wanted a look.
  • In the IDE outline: the Structure view lists all generated methods, without changing the file.
  • From the command line: run the Lombok JAR’s delombok tool, which writes plain Java versions of your sources to another folder.
Terminal
java -jar lombok.jar delombok src/main/java -d target/delombok

This is also the escape route if a team ever decides to stop using Lombok: delombok the code once, and continue with plain Java.

17. Common Mistakes to Avoid

MistakeWhat goes wrongFix
Using @Data on JPA entitiesLazy-loading errors, endless loops, broken setsUse @Getter/@Setter and write equals/hashCode yourself
Forgetting @Builder.DefaultDefault field values are ignored by the builderAdd @Builder.Default to fields with defaults
Using @Builder alone on a class that Jackson or JPA must createNo no-argument constructor, so creating the object failsAdd @NoArgsConstructor and @AllArgsConstructor, or use @Jacksonized
Generating setters everywhereObjects can be changed from anywhereAdd setters only where needed, or use @Value
Printing passwords or tokens in toString()Secrets appear in logs@ToString.Exclude on sensitive fields
Missing annotation processor setup“cannot find symbol” errors in the buildConfigure annotationProcessorPaths (section 3)

18. Pros and Cons

ProsCons
Far less repetitive codeA build-time dependency every developer must set up
Classes stay short and readableGenerated code is not visible in the source file
Methods update automatically when fields changeOveruse, such as @Data everywhere, can cause subtle bugs
Clean constructor injection with @RequiredArgsConstructorMust be updated when you move to a new Java version
No runtime cost; not in the final JARSome teams prefer explicit code and avoid it

19. Summary

Lombok removes repetitive Java code by generating it at compile time, with no runtime cost. Set it up with the dependency, the annotation processor configuration and the JAR exclusion, and your IDE will understand it too. Use @RequiredArgsConstructor and @Slf4j for Spring beans, @Value, @Builder or records for DTOs, and only @Getter and @Setter, with hand-written equals and hashCode, for JPA entities. Chosen with care, Lombok keeps a Spring Boot codebase short, clear and easy to change.