Skip to Content
Course content

149: The Builder Pattern

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've seen this exact bug happen in production more times than I care to admit. You're working on a project with a complex data object—maybe a User profile or a Network Request—and you've got a constructor with six or seven parameters. Most of them are the same type, usually Strings.

// The "Telescoping Constructor" mess
public class EmailMessage {
    private final String sender;
    private final String recipient;
    private final String subject;
    private final String body;
    private final String cc;

    public EmailMessage(String sender, String recipient, String subject, String body, String cc) {
        this.sender = sender;
        this.recipient = recipient;
        this.subject = subject;
        this.body = body;
        this.cc = cc;
    }
}

// Somewhere in the service layer...
EmailMessage mail = new EmailMessage(
    "customer@example.com", // Wait, is this the sender or recipient?
    "support@company.com", 
    "Urgent Help", 
    "I can't log in!", 
    "manager@company.com"
);

The Silent Parameter Swap

The problem here isn't that the code is "broken" in a way that causes a crash. It's that it's broken in a way that the compiler loves. If you accidentally swap the sender and the recipient, Java doesn't care—they're both Strings. You'll ship the code, the tests might pass if you aren't checking the specific direction of the email, and then you'll realize a week later that your customers are receiving emails meant for your support team.

As you add more optional fields (like bcc, priority, or attachments), you either end up with a massive constructor that's impossible to read, or a "telescoping" nightmare where you have five different constructor overloads to handle every possible combination of optional parameters. It's a maintenance disaster.

Creating a Fluent Builder

The fix is the Builder Pattern. Instead of forcing the caller to remember the exact order of seven different Strings, we create a helper class that lets us set the values explicitly. I prefer the "Fluent" style because it reads like a sentence.

public class EmailMessage {
    private final String sender;
    private final String recipient;
    private final String subject;
    private final String body;
    private final String cc;

    // We make the constructor private so no one can bypass the builder
    private EmailMessage(Builder builder) {
        this.sender = builder.sender;
        this.recipient = builder.recipient;
        this.subject = builder.subject;
        this.body = builder.body;
        this.cc = builder.cc;
    }

    public static class Builder {
        private String sender;
        private String recipient;
        private String subject;
        private String body;
        private String cc;

        public Builder setSender(String sender) {
            this.sender = sender;
            return this; // This is the secret sauce for chaining
        }

        public Builder setRecipient(String recipient) {
            this.recipient = recipient;
            return this;
        }

        public Builder setSubject(String subject) {
            this.subject = subject;
            return this;
        }

        public Builder setBody(String body) {
            this.body = body;
            return this;
        }

        public Builder setCc(String cc) {
            this.cc = cc;
            return this;
        }

        public EmailMessage build() {
            return new EmailMessage(this);
        }
    }
}

Why this solves the cognitive load

Now, look at how we instantiate the object. The ambiguity is gone. Even if you're a junior developer who has never seen the EmailMessage class before, you know exactly what is being assigned to what.

EmailMessage mail = new EmailMessage.Builder()
    .setSender("support@company.com")
    .setRecipient("customer@example.com")
    .setSubject("Urgent Help")
    .setBody("I can't log in!")
    .build();

A few things to notice here that make this a professional implementation:

  • Immutability: The fields in EmailMessage remain final. We aren't using setters on the main object, which means once the email is built, it can't be accidentally changed.
  • Readability: The return this; in the builder methods allows us to chain calls. I call this "fluent" because the code almost reads like a configuration file.
  • Validation: If you need to ensure that sender and recipient are never null, the build() method is the perfect place to put that logic. You can throw an IllegalStateException right there before the object is even created.



📋 Practical Task

Exercise: Building a Custom PC Configuration System

You are building a system for a custom PC shop. A Computer object can have a variety of components, some of which are required and some of which are optional. Using the Builder Pattern, implement the following:

  1. Create a Computer class with the following final fields:
    • cpu (String) - Required
    • ramSize (int) - Required
    • gpu (String) - Optional
    • storage (String) - Optional
    • isOverclocked (boolean) - Optional (defaults to false)
  2. Implement a static inner Builder class.
  3. Ensure the Computer constructor is private.
  4. Add a validation check inside the build() method: if cpu is null or ramSize is less than or equal to 0, throw an IllegalStateException("Invalid PC configuration").
  5. In a main method, instantiate one Computer using the builder with all fields set, and another with only the required fields.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.