Java programming language logo

Builder Design Pattern

The Builder pattern comes handy when the instantiation of an object should happen in several steps, or it should be controlled by algorithms.

The Builder pattern can be also used when a complex object can be instantiated in different ways. Without the pattern, the class should have a lots of constructor.

Builder is a creational pattern.

Example

In the next Java example, we are going to create a Person class. This class have several fields, but most of them are not mandatory. It has only one constructor that is private and accepts a Builder class.

The Builder class has the same fields as the Person class. This is instantiated first, and the required values can be set on this instance. The set... methods return with this, so they can be called one after another like builder.setBirthYear(y).setBirthMonth(m) and so on.

The Builder class has a build method that makes some validations, calls the Person class’s constructor and passes the Builder instance to it. Finally, the build method returns with an instance of Person.


package com.programcodex.designpatterns.builder;

import java.time.Month;
import java.time.format.TextStyle;
import java.util.Locale;

public class Person {

    private final String firstName;
    private final String middleName;
    private final String lastname;
    private final int birthYear;
    private final Month birthMonth;
    private final int birthDay;
    private final String birthPlace;

    public static class Builder {

        private final String firstName;
        private String middleName;
        private final String lastname;
        private int birthYear;
        private Month birthMonth;
        private int birthDay;
        private String birthPlace;

        public Builder(String firstName, String lastname) {
            this.firstName = firstName;
            this.lastname = lastname;
        }
        
        public Builder setMiddleName(String middleName) {
            this.middleName = middleName;
            return this;
        }

        public Builder setBirthYear(int birthYear) {
            this.birthYear = birthYear;
            return this;
        }

        public Builder setBirthMonth(Month birthMonth) {
            this.birthMonth = birthMonth;
            return this;
        }

        public Builder setBirthDay(int birthDay) {
            this.birthDay = birthDay;
            return this;
        }

        public Builder setBirthPlace(String birthPlace) {
            this.birthPlace = birthPlace;
            return this;
        }

        public Person build() {
            if (isEmpty(firstName) || isEmpty(lastname) 
                    || birthYear == 0) {
                String exMsg = "First name, last name and birth year are mandatory!";
                throw new IllegalStateException(exMsg);
            }

            return new Person(this);
        }

        private boolean isEmpty(String s) {
            return s == null ||  "".equals(s.trim());
        }
    }

    private Person(Builder builder) {
        this.firstName = builder.firstName;
        this.middleName = builder.middleName;
        this.lastname = builder.lastname;
        this.birthYear = builder.birthYear;
        this.birthMonth = builder.birthMonth;
        this.birthDay = builder.birthDay;
        this.birthPlace = builder.birthPlace;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(firstName).append(' ');

        if (middleName != null) {
            sb.append(middleName).append(' ');
        }

        sb.append(lastname).append('\n');
        
        if (birthMonth != null && birthDay != 0) {
            sb.append("Birthday: ").append(birthDay).append(' ')
            .append(birthMonth.getDisplayName(TextStyle.FULL, Locale.ENGLISH))
            .append(' ').append(birthYear).append('\n');
        } else {
            sb.append("Born in ").append(birthYear).append('\n');
        }
        
        if(birthPlace != null){
            sb.append("Birthplace: ").append(birthPlace).append('\n');
        }
      
        return sb.toString();
    }
}

And we are done. It’s time to run these things.

In the test class, we are creating three Person instance. For the first person almost all of the fields are set. For the second person only some of them. The third person’s data is not sufficient, so the creation fails at this.


package com.programcodex.designpatterns.builder;

import java.time.Month;

public class TestBuilder {

    public static void main(String[] args) {
        // Freddie Mercury
        Person.Builder builder = new Person.Builder("Freddie", "Mercury");
        builder.setBirthYear(1946).setBirthMonth(Month.SEPTEMBER);
        builder.setBirthDay(5).setBirthPlace("Stone Town of Zanzibar");

        Person freddie = builder.build();
        System.out.println(freddie);
        
        // Johann Sebastian Bach
        Person bach = new Person.Builder("Johann", "Bach")
                .setMiddleName("Sebastian").setBirthYear(1685).build();
        System.out.println(bach);
        
        // Nobody
        builder = new Person.Builder("Nobody", " ");
        
        try {
            Person nobody = builder.build();
        } catch(IllegalStateException ex) {
            System.out.println(ex.getMessage());
        }
    }    
}

The output of the above test class:

The Builder Design Pattern