Skip to content

Builder

The Builder pattern is another way to create objects. It is most often used to create objects that contain many fields. Objects that contain many fields (e.g. 4 or more) can also be created using constructors, but then if we want to initialize only some of these fields:

  • we have to write a very large number of constructors.
  • we could pass null in the constructor (using null as arguments is a bad approach).
  • we have to remember the order of arguments in the constructor.

The Builder pattern eliminates all these problems.

Construction

In order to create a builder class for a certain class, we have to create:

  • a static class inside the class
  • or a separate class

Both approaches are similar, but the first one prevents users from using the constructor to create a target object. It doesn't matter which one you choose, we always have to implement:

  • "configuring" methods for each field, which look like regular setters, but also give the option of calling the next builder method after the dot.
  • the "build" method, which creates the target object based on the values set by the "configuring" methods.

With this approach, we can call any subset of "configuring" methods in any order.

builder

ATTENTION: To call subsequent methods after the "configuring" method, each of them must return the this reference.

Inside class builder

The following example shows an example implementation of the Builder pattern.

package pl.sdacademy;

import java.util.ArrayList;
import java.util.List;

public class Weapon {

  private String type;
  private String name;
  private Integer damage;
  private Long durability;
  private List<String> perks;

  private Weapon(final String type, final String name, final Integer damage, final Long durability, final List<String> perks) {
    this.type = type;
    this.name = name;
    this.damage = damage;
    this.durability = durability;
    this.perks = perks;
  }

  public String getType() {
    return type;
  }

  public void setType(final String type) {
    this.type = type;
  }

  public String getName() {
    return name;
  }

  public void setName(final String name) {
    this.name = name;
  }

  public Integer getDamage() {
    return damage;
  }

  public void setDamage(final Integer damage) {
    this.damage = damage;
  }

  public Long getDurability() {
    return durability;
  }

  public void setDurability(final Long durability) {
    this.durability = durability;
  }

  public List<String> getPerks() {
    return perks;
  }

  public void setPerks(final List<String> perks) {
    this.perks = perks;
  }

  public static class Builder {
    private String type;
    private String name;
    private Integer damage;
    private Long durability;
    private List<String> perks = new ArrayList<>();

    // configuring methods
    public Builder withType(final String type) {
      this.type = type;
      return this;
    }

    public Builder withName(final String name) {
      this.name = name;
      return this;
    }

    public Builder withDamage(final Integer damage) {
      this.damage = damage;
      return this;
    }

    public Builder withDurability(final Long durability) {
      this.durability = durability;
      return this;
    }

    public Builder withPerks(final List<String> perks) {
      this.perks = perks;
      return this;
    }

    // creates the target object
    public Weapon build() {
      return new Weapon(type, name, damage, durability, perks);
    }
  }
}
public class WeaponUsage {
  public static void main(String[] args) {
    final Weapon laserGun = new Weapon.Builder()
        .withDamage(123)
        .withName("LaserGun")
        .withPerks(List.of("Color:red"))
        .withDurability(50L)
        .build();
  }
}

In the example above, notice that the constructor of the Weapon class is private. The Builder class is a static class defined inside theWeapon class. This class has access to all fields and methods (even private) defined directly in the Weapon class. The Weapon class in this case can only be created with the help of a builder. Access to the static class defined in the class is obtained exactly the same as to the static field, i.e. after the . character, e.g.Weapon.Builder. The Builder class has a default public constructor, which we can use as follows: new Weapon.Builder().

ATTENTION: "Configuring" methods can have any name. One convention is to use the word 'with' in their name. The build method is often called build or create but its name is also unrestricted.

Builder in a separate class

We can also create a Builder in a separate class, e.g.:

package pl.sdacademy;

public class Toy {
  private String name;
  private String type;
  private String madeOf;

  Toy(final String name, final String type, final String madeOf) {
    this.name = name;
    this.type = type;
    this.madeOf = madeOf;
  }

  public String getName() {
    return name;
  }

  public void setName(final String name) {
    this.name = name;
  }

  public String getType() {
    return type;
  }

  public void setType(final String type) {
    this.type = type;
  }

  public String getMadeOf() {
    return madeOf;
  }

  public void setMadeOf(final String madeOf) {
    this.madeOf = madeOf;
  }
}
package pl.sdacademy;

public class ToyBuilder {
  private String name;
  private String type;
  private String madeOf;

  public ToyBuilder withName(final String name) {
    this.name = name;
    return this;
  }

  public ToyBuilder withType(final String type) {
    this.type = type;
    return this;
  }

  public ToyBuilder withMadeOf(final String madeOf) {
    this.madeOf = madeOf;
    return this;
  }

  public Toy build() {
    return new Toy(name, type, madeOf);
  }
}
package pl.sdacademy;

public class ToyBuilderUsage {
  public static void main(String[] args) {
    final Toy toy = new ToyBuilder()
        .withMadeOf("plastic")
        .withName("Matchbox car")
        .withType("Small car")
        .build();
  }
}

Note that the ToyBuilder class uses the constructor of theToy class, which has the package private (default) access. ToyBuilder class is in the same package as the Toy class. If these classes are in different packages, the constructor of the Toy class have to be public.

Builder with Lombok use

The builder implementations are very similar for each class. The creators of the lombok library wanted to remove the need to write the builder code, it can be generated using the @Builder annotation. The following example also uses other annotations from this library:

  • @Data - generates getters and setters for all fields, along with toString, equals and hashCode methods
  • @NoArgsConstructor - generates a no-argument constructor
  • @AllArgsConstructor - generates a constructor for all the fields of the class
package pl.sdacademy;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class GraphicsCard {
  private int memoryInMb;
  private String producer;
  private String series;
  private String modelName;
}
package pl.sdacademy;

public class LombokBuilderUsage {
  public static void main(String[] args) {
    GraphicsCard graphicsCard = GraphicsCard.builder()
        .memoryInMb(2048)
        .modelName("GF1660")
        .producer("Asus")
        .series("1xxx")
        .build();
  }
}

Remember that to get to the lombok generated builder, we need to call the static builder method.