Builder Pattern
The Builder Pattern is a creational design pattern used to construct a complex object step by step. It separates the construction of a complex object from its representation, so the same construction process can create different representations.
This pattern is extremely useful when you need to create an object with many possible configuration options or optional fields. It helps avoid the "telescoping constructor" anti-pattern (having multiple constructors with different parameter lists) and improves code readability.
When to Use It
- When the constructor for a class would have a large number of parameters, most of which might be optional.
- When you want to create an object that requires a multi-step setup.
- When you need to create immutable objects without having a complex, all-encompassing constructor.
Structure
- Product: The complex object that is being built.
- Builder: An interface that specifies the steps for building the
Product. - ConcreteBuilder: Implements the
Builderinterface and provides the logic for constructing the parts of theProduct. It keeps track of the representation it's creating. - Director (Optional): A class that defines the order in which to execute the building steps. It works with a builder object through the common
Builderinterface.
In modern Java, the Director is often omitted, and the client code calls the builder's steps directly in a fluent "chaining" style.
Example: Building an Email Object
Consider an Email object that can have many optional fields like cc, bcc, subject, and body. Using a constructor would be cumbersome. The Builder pattern is a perfect fit.
1. Product Class (Email)
This is the complex object we want to create. Note that its constructor is private, forcing the use of the builder.
package com.example.patterns.builder;
public class Email {
private final String to; // required
private final String from; // required
private final String subject; // optional
private final String body; // optional
private final String cc; // optional
private final String bcc; // optional
// Private constructor that takes a builder
private Email(EmailBuilder builder) {
this.to = builder.to;
this.from = builder.from;
this.subject = builder.subject;
this.body = builder.body;
this.cc = builder.cc;
this.bcc = builder.bcc;
}
@Override
public String toString() {
return "Email{" +
"to='" + to + "\'" +
", from='" + from + "\'" +
", subject='" + subject + "\'" +
", body='" + body + "\'" +
", cc='" + cc + "\'" +
", bcc='" + bcc + "\'" +
"}";
}
// Static nested Builder class
public static class EmailBuilder {
private final String to;
private final String from;
private String subject;
private String body;
private String cc;
private String bcc;
// Builder constructor for the required fields
public EmailBuilder(String to, String from) {
this.to = to;
this.from = from;
}
// Setter methods for optional fields that return the builder
public EmailBuilder withSubject(String subject) {
this.subject = subject;
return this; // Return the builder for method chaining
}
public EmailBuilder withBody(String body) {
this.body = body;
return this;
}
public EmailBuilder withCc(String cc) {
this.cc = cc;
return this;
}
public EmailBuilder withBcc(String bcc) {
this.bcc = bcc;
return this;
}
// The final build() method that returns the Email object
public Email build() {
return new Email(this);
}
}
}
2. Putting It All Together
The client code uses the EmailBuilder to construct an Email object in a highly readable, fluent way.
package com.example.patterns.builder;
public class BuilderDemo {
public static void main(String[] args) {
// Create a simple email with only required fields
Email simpleEmail = new Email.EmailBuilder("recipient@example.com", "sender@example.com")
.build();
System.out.println("Simple Email: " + simpleEmail);
System.out.println("---");
// Create a complex email with several optional fields
Email complexEmail = new Email.EmailBuilder("recipient@example.com", "sender@example.com")
.withSubject("Project Update")
.withBody("Here is the latest update on the project...")
.withCc("manager@example.com")
.build();
System.out.println("Complex Email: " + complexEmail);
}
}
Output:
Simple Email: Email{to='recipient@example.com', from='sender@example.com', subject='null', body='null', cc='null', bcc='null'}
---
Complex Email: Email{to='recipient@example.com', from='sender@example.com', subject='Project Update', body='Here is the latest update on the project...', cc='manager@example.com', bcc='null'}
The Modern Approach: Project Lombok
Manually writing a builder class can be repetitive. Project Lombok is a popular library that can automatically generate the builder pattern for you with a single annotation.
To use it, you would add the Lombok dependency to your pom.xml and write your Email class like this:
import lombok.Builder;
import lombok.ToString;
@Builder
@ToString
public class EmailLombok {
@Builder.Default private final String from = "default.sender@example.com";
private final String to;
private final String subject;
private final String body;
private final String cc;
private final String bcc;
}
Lombok automatically generates the EmailLombokBuilder class and the builder() method at compile time, which you can use just like the manual version:
EmailLombok email = EmailLombok.builder()
.to("recipient@example.com")
.subject("Hello from Lombok!")
.build();
System.out.println(email);
This significantly reduces boilerplate code and is the preferred way to implement the Builder pattern in modern Java projects.