Backend Development 10 min read

Why Lombok @Builder Is Discouraged and @Accessors Is a Better Alternative

The article explains the pitfalls of Lombok's @Builder annotation—such as loss of required‑parameter enforcement, mutable builders, and limited flexibility—and demonstrates how using @Accessors (or manually crafted fluent setters) provides a safer, more expressive way to construct Java objects.

Java Architect Essentials
Java Architect Essentials
Java Architect Essentials
Why Lombok @Builder Is Discouraged and @Accessors Is a Better Alternative

In this article the author explains why the Lombok @Builder annotation is discouraged and proposes @Accessors as a better alternative.

Scenario reproduction

First, a plain class without @Builder is shown, followed by a usage example that manually creates the response object.

package io.gitrebase.demo;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class APIResponse
{
    private T payload;
    private Status status;
}

// usage example
package io.gitrebase.demo;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@Slf4j
@RestControllerAdvice(assignableTypes = io.gitrebase.demo.RestApplication.class)
public class ApplicationExceptionHandler {
    @ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR)
    public APIResponse handleException(Exception exception) {
        log.error("Unhandled Exception", exception);
        Status status = new Status();
        status.setResponseCode("RESPONSE_CODE_IDENTIFIER");
        status.setDescription("Bla Bla Bla");
        APIResponse response = new APIResponse();
        response.setStatus(status);
        return response;
    }
}

Then the same class is annotated with @Builder and a usage example that builds the object via the generated builder is presented.

package io.gitrebase.demo;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
public class APIResponse
{
    private T payload;
    private Status status;
}

// usage example
package io.gitrebase.demo;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@Slf4j
@RestControllerAdvice(basePackageClasses = io.gitrebase.demo.RestApplication.class)
public class ApplicationExceptionHandler {
    @ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR)
    public APIResponse handleException(Exception exception) {
        log.error("Unhandled Exception", exception);
        return APIResponse.builder()
            .status(Status.builder()
                .responseCode("RESPONSE_CODE_IDENTIFIER")
                .description("Bla Bla Bla")
                .build())
            .build();
    }
}

Why not recommend @Builder?

@Builder generates an imperfect builder that cannot distinguish required from optional parameters, allows mutable state via generated setters, produces a concrete builder type that lacks flexibility, and is only suitable for objects with many optional fields.

Alternative: @Accessors

The @Accessors(chain = true) annotation generates fluent setters that enable method chaining while keeping the class simple. The article shows the class definition, the compiled version, and a usage example.

package io.gitrebase.demo;

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

@Data
@Accessors(chain = true)
public class APIResponse
{
    private T payload;
    private Status status;
}
package io.gitrebase.demo;

import lombok.experimental.Accessors;

@Accessors(chain = true)
public class APIResponse
{
    private T payload;
    private Status status;

    public T getPayload() { return this.payload; }
    public APIResponse
setPayload(T payload) { this.payload = payload; return this; }
    public Status getStatus() { return this.status; }
    public APIResponse
setStatus(Status status) { this.status = status; return this; }
}
package io.gitrebase.demo;

import lombok.extern.slf4j.Slf4j;
import lombok.var;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@Slf4j
@RestControllerAdvice(basePackageClasses = io.gitrebase.demo.RestApplication.class)
public class ApplicationExceptionHandler {
    @ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR)
    public APIResponse handleException(Exception exception) {
        log.error("Unhandled Exception", exception);
        var status = new Status()
            .setResponseCode("RESPONSE_CODE_IDENTIFIER")
            .setDescription("Bla Bla Bla");
        return new APIResponse().setStatus(status);
    }
}

For cases where some parameters are mandatory and others optional, a constructor can be used for required fields while @Accessors provides fluent setters for optional ones.

// class with required fields in constructor and optional fields via Accessors
import lombok.Data;
import lombok.experimental.Accessors;

@Getter
@Accessors(chain = true)
public class Person {
    private String name; // required
    private int id;      // required
    private int age;    // optional
    private String address; // optional

    public Person(String name, int id) {
        this.name = name;
        this.id = id;
    }
}

// usage
Person person = new Person("Zhang San", 1001)
    .setAge(25)
    .setAddress("Beijing");
System.out.println(person);

Other notes

The author notes that @Accessors is stable in the lombok.experimental package, and if Lombok is not desired, developers can manually implement fluent setters.

Finally, readers are encouraged to read Lombok's source code to understand generated methods and to consider best practices when choosing annotations.

design patternsJavaBackend DevelopmentLombokBuilderAccessors
Java Architect Essentials
Written by

Java Architect Essentials

Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.