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.
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.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.