Conceito e intenção
Builder constrói objetos complexos passo a passo, separando a lógica de construção da representação final. O padrão é especialmente útil quando o objeto tem muitos parâmetros — especialmente opcionais — e quando se quer garantir que o objeto só exista em estado válido e imutável.
O problema motivador é o construtor telescópico: à medida que os parâmetros crescem, criam-se múltiplas sobrecargas (new Order(id, customer), new Order(id, customer, discount), new Order(id, customer, discount, coupon) …). Isso é ilegível e propenso a erros de posição. A alternativa com setters cria objetos mutáveis que podem ser usados incompletos.
Builder elimina os dois problemas: o objeto só nasce quando .build() é chamado (e as validações rodam nesse momento), e pode ser imutável porque nunca é exposto em estado parcial.
Estrutura
┌──────────────┐ ┌─────────────────────┐
│ Director │─────▶│ <<interface>> │
│ (opcional) │ │ Builder │
└──────────────┘ │ + setPartA() │
│ + setPartB() │
│ + build(): Product │
└──────────┬──────────┘
▲
┌────────┴────────┐
│ ConcreteBuilder │
│ - fields... │
│ + build() │
└─────────────────┘Participantes:
- Builder — interface com métodos de configuração
- ConcreteBuilder — acumula estado e cria o produto em
build() - Product — o objeto imutável resultante
- Director — (opcional) orquestra sequências de build reutilizáveis
Implementação Java — fluent API com inner class
public final class OrderRequest {
// Campos imutáveis
private final String customerId;
private final List<OrderItem> items;
private final String couponCode; // opcional
private final Address shippingAddress;
private final PaymentMethod paymentMethod;
private final boolean expressDelivery; // padrão: false
private final String notes; // opcional
// Construtor privado — só Builder pode chamar
private OrderRequest(Builder builder) {
this.customerId = builder.customerId;
this.items = List.copyOf(builder.items);
this.couponCode = builder.couponCode;
this.shippingAddress = builder.shippingAddress;
this.paymentMethod = builder.paymentMethod;
this.expressDelivery = builder.expressDelivery;
this.notes = builder.notes;
}
// Getters somente — sem setters
public String getCustomerId() { return customerId; }
public List<OrderItem> getItems() { return items; }
public Optional<String> getCouponCode() { return Optional.ofNullable(couponCode); }
public Address getShippingAddress() { return shippingAddress; }
public PaymentMethod getPaymentMethod() { return paymentMethod; }
public boolean isExpressDelivery() { return expressDelivery; }
public Optional<String> getNotes() { return Optional.ofNullable(notes); }
public static Builder builder(String customerId) {
return new Builder(customerId);
}
public static final class Builder {
private final String customerId;
private final List<OrderItem> items = new ArrayList<>();
private String couponCode;
private Address shippingAddress;
private PaymentMethod paymentMethod;
private boolean expressDelivery = false;
private String notes;
private Builder(String customerId) {
this.customerId = Objects.requireNonNull(customerId, "customerId é obrigatório");
}
public Builder addItem(String sku, int quantity) {
this.items.add(new OrderItem(sku, quantity));
return this;
}
public Builder coupon(String couponCode) {
this.couponCode = couponCode;
return this;
}
public Builder shippingAddress(Address address) {
this.shippingAddress = address;
return this;
}
public Builder paymentMethod(PaymentMethod method) {
this.paymentMethod = method;
return this;
}
public Builder expressDelivery() {
this.expressDelivery = true;
return this;
}
public Builder notes(String notes) {
this.notes = notes;
return this;
}
// Validação centralizada antes de criar o objeto
public OrderRequest build() {
if (items.isEmpty()) {
throw new IllegalStateException("Pedido deve ter pelo menos um item");
}
Objects.requireNonNull(shippingAddress, "Endereço de entrega é obrigatório");
Objects.requireNonNull(paymentMethod, "Método de pagamento é obrigatório");
return new OrderRequest(this);
}
}
}
// Uso — legível, sem ambiguidade de posição
OrderRequest order = OrderRequest.builder("customer-123")
.addItem("PROD-001", 2)
.addItem("PROD-002", 1)
.coupon("DESCONTO10")
.shippingAddress(Address.of("Rua das Flores", "123", "São Paulo"))
.paymentMethod(PaymentMethod.CREDIT_CARD)
.expressDelivery()
.notes("Deixar com porteiro")
.build();Builder com validação avançada e step builder
Step Builder força a sequência obrigatória de chamadas em tempo de compilação:
// Interfaces representam os "passos" obrigatórios
public interface CustomerStep { AddressStep forCustomer(String customerId); }
public interface AddressStep { PaymentStep deliverTo(Address address); }
public interface PaymentStep { FinalStep withPayment(PaymentMethod method); }
public interface FinalStep {
FinalStep addItem(String sku, int qty);
FinalStep coupon(String code);
FinalStep expressDelivery();
OrderRequest build();
}
// Implementação única que implementa todas as interfaces
public class OrderRequestStepBuilder
implements CustomerStep, AddressStep, PaymentStep, FinalStep {
// campos e implementação dos métodos...
}
// Uso — o compilador impede ordem errada
OrderRequest order = OrderRequest.steps() // retorna CustomerStep
.forCustomer("customer-123") // retorna AddressStep
.deliverTo(address) // retorna PaymentStep
.withPayment(PaymentMethod.CREDIT_CARD) // retorna FinalStep
.addItem("PROD-001", 2)
.build();
// Erro de compilação: não dá para chamar .build() antes de forCustomer()Lombok @Builder
Em projetos com Lombok, o boilerplate é gerado automaticamente:
@Builder
@Value // imutável: todos os campos final, getters gerados
public class OrderRequest {
@NonNull String customerId;
@Singular List<OrderItem> items; // gera addItem() e items()
String couponCode;
@NonNull Address shippingAddress;
@NonNull PaymentMethod paymentMethod;
@Builder.Default boolean expressDelivery = false;
String notes;
}
// Uso idêntico ao manual
OrderRequest order = OrderRequest.builder()
.customerId("customer-123")
.item(new OrderItem("PROD-001", 2))
.item(new OrderItem("PROD-002", 1))
.shippingAddress(address)
.paymentMethod(PaymentMethod.CREDIT_CARD)
.build();Para validação customizada com Lombok, use @Builder + método build() manual via @Builder(buildMethodName = "create") e método público build() que valida antes de chamar create().
Implementação TypeScript
// Versão TypeScript com fluent interface
class OrderRequest {
readonly customerId: string;
readonly items: ReadonlyArray<OrderItem>;
readonly couponCode?: string;
readonly shippingAddress: Address;
readonly paymentMethod: PaymentMethod;
readonly expressDelivery: boolean;
readonly notes?: string;
private constructor(builder: OrderRequestBuilder) {
this.customerId = builder['_customerId'];
this.items = [...builder['_items']];
this.couponCode = builder['_couponCode'];
this.shippingAddress = builder['_shippingAddress']!;
this.paymentMethod = builder['_paymentMethod']!;
this.expressDelivery = builder['_expressDelivery'];
this.notes = builder['_notes'];
}
static builder(customerId: string): OrderRequestBuilder {
return new OrderRequestBuilder(customerId);
}
}
class OrderRequestBuilder {
private _customerId: string;
private _items: OrderItem[] = [];
private _couponCode?: string;
private _shippingAddress?: Address;
private _paymentMethod?: PaymentMethod;
private _expressDelivery = false;
private _notes?: string;
constructor(customerId: string) {
if (!customerId) throw new Error('customerId é obrigatório');
this._customerId = customerId;
}
addItem(sku: string, quantity: number): this {
this._items.push({ sku, quantity });
return this;
}
coupon(code: string): this { this._couponCode = code; return this; }
deliverTo(addr: Address): this { this._shippingAddress = addr; return this; }
payWith(pm: PaymentMethod): this { this._paymentMethod = pm; return this; }
express(): this { this._expressDelivery = true; return this; }
withNotes(notes: string): this { this._notes = notes; return this; }
build(): OrderRequest {
if (this._items.length === 0) throw new Error('Pedido sem itens');
if (!this._shippingAddress) throw new Error('Endereço obrigatório');
if (!this._paymentMethod) throw new Error('Pagamento obrigatório');
return new (OrderRequest as any)(this);
}
}
// Uso
const order = OrderRequest.builder('customer-123')
.addItem('PROD-001', 2)
.deliverTo({ street: 'Rua das Flores', number: '123', city: 'SP' })
.payWith('CREDIT_CARD')
.express()
.build();No mundo real
StringBuilder / StringBuffer no Java SDK são builders clássicos — append(), insert(), delete() retornam this; toString() é o equivalente de build().
HttpRequest.Builder (Java 11 java.net.http) — fluent builder para construir requisições HTTP: .uri(), .header(), .POST(body), .timeout(), .build().
Spring MockMvcRequestBuilders em testes:
mockMvc.perform(post("/orders")
.contentType(APPLICATION_JSON)
.header("Authorization", token)
.content(json))
.andExpect(status().isCreated());UriComponentsBuilder do Spring — builder para construir URLs de forma segura, evitando concatenação de strings com query params.
Criteria / CriteriaBuilder do JPA — builder para queries dinâmicas sem concatenação de JPQL.
RestTemplate e WebClient (Spring WebFlux) usam builders para configuração:
WebClient client = WebClient.builder()
.baseUrl("https://api.example.com")
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.filter(ExchangeFilterFunctions.basicAuthentication(user, pass))
.build();Kotlin DSL / Gradle usa builders extensivamente — dependencies { implementation(...) } é açúcar sintático sobre builders de configuração.
Quando usar
- Objetos com 4 ou mais parâmetros, especialmente quando muitos são opcionais
- Objetos que precisam ser imutáveis (sem setters) mas têm construção complexa
- Quando a ordem dos parâmetros é ambígua (dois
String, doisbooleando mesmo tipo) - Quando a validação deve ocorrer no momento de criação, não após
- Quando o mesmo processo de construção deve produzir representações diferentes (Director + Builders distintos)
Quando NÃO usar
- Objetos simples com 1-3 parâmetros bem definidos — construtor direto é mais legível
- Quando Lombok
@Builderou Records (Java 14+) já resolvem o problema sem boilerplate manual - Quando o objeto é mutável por design — Builder não acrescenta valor para objetos que precisam de setters
- Quando a ordem dos passos não tem semântica — se qualquer ordem é válida, um construtor com parâmetros nomeados (Kotlin, Python) é equivalente sem a cerimônia do builder
Combinações com outros padrões
Builder + Factory Method: A Factory decide qual Builder instanciar; o Builder cuida da construção complexa. Útil quando há múltiplas variações do produto:
OrderRequestBuilder builder = builderFactory.create(orderType); // factory
OrderRequest order = builder.withDefaults(customer).addItem(item).build(); // builderBuilder + Composite: Quando o produto é uma estrutura em árvore (documento HTML, configuração aninhada), o Builder pode construir nós recursivamente, formando um Composite.
Builder + Template Method: O método build() pode seguir o padrão Template Method — executa passos fixos (validar, normalizar, criar) mas permite que subclasses do Builder sobrescrevam etapas específicas.