The Builder pattern in Java — yet another Builder Pattern article?

Serhii Prodan
6 min readApr 1, 2019
Photo by Todd Quackenbush on Unsplash

Yet another “Builder pattern article”? Not quite so. At least half of the explanations and tutorials on the design patters, and the Builder pattern itself, do not cover the underlying problem that this or that pattern is aiming to solve. As a self-taught programmer I am always trying to understand first why I’m doing something, rather than how, and nowadays people are more focused on how’s rather than why’s, which often leads to misunderstanding the core problem and as a consequence — incorrect solution.

The problem

In OOP more often than not we have classes holding some data that we are setting and later accessing. Creating instances of such classes might be a bit of a pain sometimes. Let’s consider the following Pizza class:

public class Pizza {
private String pizzaSize;
private int cheeseCount;
private int pepperoniCount;
private int hamCount;
// constructor, getters, setters
}

A very simple class at first glance, but there are a few caveats, for example three int fields coming one after the other, or the number of the fields (Although at this point this might not look like a big issue, but it will be soon enough.)

Any of our pizzas will have a size, however when it comes to the toppings, some or all can be present, or none at all, therefore some of the properties of our class are optional and some are mandatory.

Constructor overloading pattern.

Creating the class like new Pizza("small", 1, 0, 0) every time I want to simply get a pizza object with cheese and nothing else does not sound like a good idea to me. And here comes the first common solution - constructor overloading.

public class Pizza {
private String pizzaSize; // mandatory
private int cheeseCount; // optional
private int pepperoniCount; // optional
private int hamCount; // optional
public Pizza(String pizzaSize) {
this(pizzaSize, 0, 0, 0);
}
public Pizza(String pizzaSize, int cheeseCount) {
this(pizzaSize, cheeseCount, 0, 0);
}
public Pizza(String pizzaSize, int cheeseCount, int pepperoniCount) {
this(pizzaSize, cheeseCount, pepperoniCount, 0);
}
public Pizza(String pizzaSize, int cheeseCount, int pepperoniCount, int hamCount) {
this.pizzaSize = pizzaSize;
this.cheeseCount = cheeseCount;
this.pepperoniCount = pepperoniCount;
this.hamCount = hamCount;
}
// getters
}

This however solves the problem only partially. We can’t, for example, create a pizza with cheese and ham, but without pepperoni like this: new Pizza("small", 1, 1) as the third argument of the constructor is pepperoni. With this method we need to use the constructor containing the least number of arguments and all arguments that we need: new Pizza("small, 1, 0, 1).

If the number of our parameters grows so does the umber of overridden constructors:

public class Pizza {
private String pizzaSize; // mandatory
private String crust; // mandatory
private int cheeseCount; // optional
private int pepperoniCount; // optional
private int hamCount; // optional
private int mushroomsCount; // optional
public Pizza(String pizzaSize, String crust) {
this(pizzaSize, crust, 0, 0, 0, 0);
}
public Pizza(String pizzaSize, String crust, int cheeseCount) {
this(pizzaSize, crust, cheeseCount, 0, 0, 0);
}
public Pizza(String pizzaSize, String crust, int cheeseCount, int pepperoniCount) {
this(pizzaSize, crust, cheeseCount, pepperoniCount, 0, 0);
}
public Pizza(String pizzaSize, String crust, int cheeseCount, int pepperoniCount, int hamCount) {
this(pizzaSize, crust, cheeseCount, pepperoniCount, hamCount, 0);
}
public Pizza(String pizzaSize, String crust, int cheeseCount, int pepperoniCount, int hamCount, int mushroomsCount) {
this.pizzaSize = pizzaSize;
this.crust = crust;
this.cheeseCount = cheeseCount;
this.pepperoniCount = pepperoniCount;
this.hamCount = hamCount;
this.mushroomsCount = mushroomsCount;
}
// getters
}

And if someone switches the parameters order in the constructor?

public Pizza(String pizzaSize, String crust, int hamCount, int mushroomsCount, int cheeseCount, int pepperoniCount) {
this.pizzaSize = pizzaSize;
this.crust = crust;
this.cheeseCount = cheeseCount;
this.pepperoniCount = pepperoniCount;
this.hamCount = hamCount;
this.mushroomsCount = mushroomsCount;
}

Then our small pizza with thin crust, 1 cheese and 1 pepperoni: new Pizza("small", “thin", 1, 1) might become a small pizza with thin crust, 1 ham and 1 mushrooms and our first line of defense — the compiler won’t give us any warnings at all.

All in all, overriding constructors pattern (or as it is usually referred to “telescoping constructor pattern”) works, but it is hard to maintain if the class changes and we introduce new parameters, the number of constructors will grow as well, so it doesn’t scale very well. The readability suffers a lot, and often you’re just sitting there wondering what are all those values represent that are being passed to the constructor when the object is instantiated.

JavaBeans pattern.

Photo by Daniel Ruswick on Unsplash

Another common solution is JavaBeans. First we call a parameterless constructor of the class, and then set the properties we need with the public setters:

Pizza pizza = new Pizza();
pizza.setPizzaSize("small");
pizza.setCrust("thin");
pizza.setMushroomsCount(1);
pizza.setCheeseCount(1);
// do something with pizza

This solution has none of the disadvantages of the telescoping constructor pattern. It’s easy to scale the class, easier to instantiate, albeit a bit wordier, it’s more readable and more flexible.

The pattern however has serious disadvantages of its own. The construction of the class is split into multiple calls, therefore the instance may be in a partially constructed / invalid state:

Pizza pizza = new Pizza();
pizza.setPizzaSize("medium");
pizza.setCheeseCount(1);
pizza.deliver(); // we didn't set the crust, which will probably thrown an NPE somewhere down the call

Another big disadvantage is that the JavaBeans pattern does not enforce immutability.

The solution — Builder pattern

Photo by Rick Mason on Unsplash

The Builder pattern is one of the creational design patterns in OOP, in other words, it is used to create and configure objects. Builders are a commonly used technique for eliminating multiple constructor overrides and provides a more flexible solution to creating complex objects and is often accompanied by a fluent interface pattern.

Let’s add a Builder to our Pizza class:

public class Pizza {
private String pizzaSize;
private String crust;
private int cheeseCount;
private int pepperoniCount;
private int hamCount;
private int mushroomsCount;
public static class Builder {
private String pizzaSize; // mandatory
private String crust; // mandatory
private int cheeseCount = 0; // optional
private int pepperoniCount = 0; // optional
private int hamCount = 0; // optional
private int mushroomsCount = 0; // optional
// We set these in the constructor because we want to enforce the client setting these
// as they are mandatory
public Builder(String pizzaSize, String crust) {
this.pizzaSize = pizzaSize;
this.crust = crust;
}
public Builder cheeseCount(int cheeseCount) {
this.cheeseCount = cheeseCount;
return this;
}
public Builder pepperoniCount(int pepperoniCount) {
this.pepperoniCount = pepperoniCount;
return this;
}
public Builder hamCount(int hamCount) {
this.hamCount = hamCount;
return this;
}
public Builder mushroomsCount(int mushroomsCount) {
this.mushroomsCount = mushroomsCount;
return this;
}
public Pizza build() {
return new Pizza(this);
}
}
private Pizza(Builder builder) {
this.pizzaSize = builder.pizzaSize;
this.crust = builder.crust;
this.cheeseCount = builder.cheeseCount;
this.pepperoniCount = builder.pepperoniCount;
this.hamCount = builder.hamCount;
this.mushroomsCount = builder.mushroomsCount;
}
// getters
}

We made our Pizza constructor a private one so that our class can’t be instantiated directly, at the same time we added a static Builder class with a constructor that has our mandatory parameters pizzaSize and crust, methods setting optional parameters, and finally a build() method that will return a new instance of Pizza class. The setter methods return the Builder itself thus giving us a fluent interface with method chaining.

Pizza pizza = new Pizza.Builder("large", "thin")
.cheeseCount(1)
.pepperoniCount(1)
.build();

The resulting Pizza object is immutable as compared to the above JavaBeans one. It is much easier to write and, more important, to read this code. Just like with the constructor, we can check the passed parameters for any violations, most often inside the build() method or the setter method themselves, and throw IllegalStateException if any violations are present prior to creating an instance of the class.

Though the Builder pattern does have disadvantages some minor disadvantages. First of all, you need to create a Builder object prior to creating the object of the class itself. This could potentially be a problem in some performance-critical cases.

The class becomes a bit more verbose and I wouldn’t generally use a builder if the class only has 2–3 parameters and is unlikely to change in the future. In other cases I prefer to have builders over regular constructors.

Overall, the Builder pattern is a very commonly used technique for creating objects and is a good choice to use when classes have constructors with multiple parameters (especially optional ones) and are likely to change in the future. The code becomes much easier to write and read as compared to overridden constructors, and the class scales just as well as JavaBeans but is much safer.

--

--

Serhii Prodan

Searching for the answer to the Ultimate Question by night, tester by calling, serendipitously became a devops lead by day. Automating things I get my hands on.