Why You Should Stop Using Lombok’s @Builder and What to Use Instead

This article explains the hidden pitfalls of Lombok’s @Builder annotation, why it often violates the Builder pattern, and presents @Accessors and manual fluent setters as safer, more flexible alternatives for Java backend development.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
Why You Should Stop Using Lombok’s @Builder and What to Use Instead

In the article "Oh!! Stop using @Builder", the author points out a major pitfall of Lombok’s @Builder annotation where default values are lost, and explains why @Builder often misleads developers into thinking it follows the Builder pattern.

1. Reproducing the scenario

1.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;
    }
}

1.2 Using @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 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();
    }
}

2. Why @Builder is not recommended

@Builder generates a mutable builder that cannot distinguish required from optional parameters, violates immutability, and is limited to a concrete type, which defeats the purpose of the Builder pattern.

3. Alternatives

3.1 Prefer @Accessors

Class definition with @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;
}

The compiled class shows fluent setters returning the object itself.

3.2 Manual simulation of @Accessors

When @Accessors is unavailable, you can write explicit fluent setters or use a constructor for required fields and chain optional setters.

// Example Person class
@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);

4. Takeaways

Most developers use Lombok annotations without inspecting the generated code. Understanding the underlying implementation helps avoid hidden bugs. In many cases @Accessors provides a clearer, more flexible alternative to @Builder.

References

https://blog.csdn.net/w605283073/article/details/130190814

https://medium.com/gitrebase/oh-stop-using-builder-9061a5911d8c

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Design PatternsJavalombokBuilderAccessors
Code Ape Tech Column
Written by

Code Ape Tech Column

Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn

0 followers
Reader feedback

How this landed with the community

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.