Why You Should Stop Using Lombok’s @Builder and What to Use Instead
This article explains the hidden pitfalls of Lombok’s @Builder annotation, demonstrates how it deviates from the classic Builder pattern, and presents safer alternatives such as @Accessors with practical code examples and guidance for mixing required and optional parameters in Java projects.
2. Scenario Reproduction
2.1 Without @Builder
Class definition:
package io.gitrebase.demo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class APIResponse<T> {
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;
}
}2.2 With @Builder
Class definition:
package io.gitrebase.demo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
public class APIResponse<T> {
private T payload;
private Status status;
}Usage example:
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);
}
}3. Why @Builder Is Not Recommended
@Builder generates a builder that cannot distinguish required from optional parameters, which may lead to incorrect or inconsistent object construction.
When combined with @Data, the generated builder becomes mutable with setter methods, violating the immutability principle of the Builder pattern.
The builder produced by @Builder is tied to a concrete type and cannot adapt to different parameter abstractions, limiting the flexibility that the pattern normally provides.
@Builder is only suitable for objects with many parameters where most are optional; for simple fluent object creation it is not the best choice.
4. Alternatives
4.1 Preferred: @Accessors
Class definition using Lombok’s @Accessors (chain = true):
package io.gitrebase.demo;
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
public class APIResponse<T> {
private T payload;
private Status status;
}Compiled class (relevant parts):
package io.gitrebase.demo;
import lombok.experimental.Accessors;
@Accessors(chain = true)
public class APIResponse<T> {
private T payload;
private Status status;
public T getPayload() { return this.payload; }
public APIResponse<T> setPayload(T payload) { this.payload = payload; return this; }
public Status getStatus() { return this.status; }
public APIResponse<T> setStatus(Status status) { this.status = status; return this; }
}Usage example:
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);
}
}The @Accessors annotation also supports advanced configuration options (fluent, chain, prefix). The full definition is shown below for reference:
/**
* A container for settings for the generation of getters and setters.
*
* Complete documentation is found at the Project Lombok features page for @Accessors.
*/
@Target({ElementType.TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.SOURCE)
public @interface Accessors {
/**
* If true, accessors will be named after the field without a get/set prefix.
* Default: false
*/
boolean fluent() default false;
/**
* If true, setters return this instead of void (enabling chaining).
* Default: false (unless fluent=true, then true)
*/
boolean chain() default false;
/**
* Prefixes to strip from field names when generating accessors.
*/
String[] prefix() default {};
}If a class has required parameters and optional ones, define required fields in a constructor and use @Accessors for the optional fields.
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
public class Person {
// required fields
private String name;
private int id;
// optional fields
private int age;
private String address;
public Person(String name, int id) {
this.name = name;
this.id = id;
}
}
public class Main {
public static void main(String[] args) {
Person person = new Person("Zhang San", 1001).setAge(25).setAddress("Beijing");
System.out.println(person);
}
}4.2 Manually Simulating @Accessors
For developers worried about the stability of @Accessors (it resides in lombok.experimental), the same fluent API can be achieved by manually writing setter methods that return this.
5. Takeaways
Most developers use Lombok annotations without inspecting the generated source code; spending time reading the underlying code helps avoid hidden pitfalls.
When using Lombok, you should be able to mentally reconstruct the compiled code; otherwise you will eventually encounter bugs.
Not every widely‑used feature is the best choice—consider whether a feature truly fits your design before adopting it. In many cases, @Accessors provides behavior closer to a proper Builder pattern than @Builder.
References
https://blog.csdn.net/w605283073/article/details/130190814
https://medium.com/gitrebase/oh-stop-using-builder-9061a5911d8c
Source: mingmingruyue.blog.csdn.net/article/details/132417856
Java Interview Crash Guide
Dedicated to sharing Java interview Q&A; follow and reply "java" to receive a free premium Java interview guide.
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.
