Builder Pattern
The builder pattern separates the construction of a complex object from its representation, letting you assemble it step by step through a fluent, readable API instead of a long positional constructor. Spring and its ecosystem lean on builders constantly — for your own DTOs via Lombok, and throughout the framework for URIs, HTTP clients, security users, and test requests.
Why builders
A constructor with many parameters — especially several of the same type — is unreadable and easy to get wrong:
// Which boolean is which? Easy to transpose arguments.
new UserDto(1L, "Ada", "[email protected]", true, false);
A builder names every value, makes order irrelevant, and lets callers set only what they need:
UserDto user = UserDto.builder()
.id(1L)
.name("Ada")
.email("[email protected]")
.active(true)
.build();
Lombok @Builder for DTOs and entities
In Spring Boot you almost never hand-write a builder — Lombok generates it. This is the most common use of the pattern in application code:
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class UserDto {
private Long id;
private String name;
private String email;
@Builder.Default
private boolean active = true;
}
It pairs naturally with mapping an entity to a response DTO in a service:
@Service
public class UserMapper {
public UserDto toDto(User entity) {
return UserDto.builder()
.id(entity.getId())
.name(entity.getName())
.email(entity.getEmail())
.active(entity.isActive())
.build();
}
}
The dedicated Lombok @Builder page covers @Builder.Default, @Singular for collections, and toBuilder for copy-and-modify. For DTOs specifically, see the DTO pattern.
Tip: Combine
@Builderwith@Getter(not@Setter) for immutable response DTOs. For Java records, you usually do not need a builder — the canonical constructor is already concise — though a builder still helps when a record has many optional fields.
Framework builders you use every day
Spring exposes the builder pattern through many fluent APIs. The shape is always the same: a static entry point, chained configuration calls, and a terminal build().
| Builder | What it builds | Typical use |
|---|---|---|
UriComponentsBuilder | A URI with path/query params | Constructing outbound URLs safely |
RestClient.builder() | A configured HTTP client | New synchronous HTTP clients |
WebClient.builder() | A reactive HTTP client | Non-blocking HTTP calls |
MockMvcRequestBuilders | A simulated HTTP request | Web-layer tests |
User.builder() | A Spring Security UserDetails | In-memory / test users |
ResponseEntity.ok()...build() | An HTTP response | Returning status + headers + body |
Building a URI
import org.springframework.web.util.UriComponentsBuilder;
URI uri = UriComponentsBuilder
.fromUriString("https://api.example.com")
.path("/products/{id}")
.queryParam("expand", "reviews")
.build(42);
// https://api.example.com/products/42?expand=reviews
Building an HTTP client
import org.springframework.web.client.RestClient;
RestClient client = RestClient.builder()
.baseUrl("https://api.example.com")
.defaultHeader("Accept", "application/json")
.build();
This is exactly the @Bean factory method shown in Factory & Abstract Factory — a builder producing a managed bean.
Building a Security user
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
UserDetails admin = User.builder()
.username("admin")
.password("{noop}secret") // {noop} = no encoding, demo only
.roles("ADMIN")
.build();
Building a test request
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"name\":\"Ada\"}"))
.andExpect(status().isCreated());
Note: Notice the recurring grammar: a static factory entry (
builder(),fromUriString,post), one chained call per option, and a closingbuild()(orperform). Once you recognise the pattern, every new Spring fluent API reads the same way.