{{announcement.body}}
{{announcement.title}}

Why Builder Is Often an Antipattern and How to Replace it With Fluent Builder

DZone 's Guide to

Why Builder Is Often an Antipattern and How to Replace it With Fluent Builder

Safe Alternative To Traditional Builder

· Java Zone ·
Free Resource

The Builder Pattern is extremely popular in Java applications. Unfortunately, it's often misunderstood and incorrectly applied, which results to runtime errors.

Let's remember the purpose of Builder: set only necessary fields in some object and keep remaining fields set to default values. For example, if we're preparing a configuration object, then it's convenient to change only the necessary parameters and keep other parameters set to default values. 

When Builder Is an Antipattern

Unfortunately, many developers pick only part of the Builder pattern — the ability to set fields individually. The second part — presence of reasonable defaults for remaining fields — is often ignored. As a consequence, it's quite easy to get incomplete (partially initialized) POJO. In an attempt to mitigate this issues we add checks to the build() method and get a (false) feeling of safety. Unfortunately, by this moment, the main damage is already done: checks are shifted to run time. And to make sure that everything is OK, we need to add dedicated tests to cover all execution paths in code where POJO is created.

How To Fix Builder for POJO's?

First of all, let's define the goal. The goal here is to return checks back to compile time. If code which does not build complete POJOs will not pass compilation, then there will be no need for dedicated tests, no need to perform checks in build() method. But, most importantly, we will remove a lot of mental overhead from developers. 

So, how this can be done? Probably, the most obvious way is to use the Fluent API pattern. The Fluent API has two parts (just like Builder, by the way): provide a convenient way to invoke methods in a chain (both, Fluent API and Builder are identical in this part) and restrict every subsequent call in the chain to only an allowed set of methods. 

The second part is what is most interesting for us. By limiting the set of methods that can be invoked at every step of building POJOs, we can enforce a particular sequence of calls and enable the call to the build() method only when all fields are set. This way, we shift all checks back to compile time. As a convenient side effect, we also make sure that all places where particular a POJO is built look identical. This way, it will be much simpler to spot incorrectly passed parameters or compare changes between code revisions.

To distinguish traditional Builder and Builder with Fluent API, I'll call latter the Fluent Builder.

Deriving Concise Fluent Builder

Let's assume that we want to create a Fluent Builder for simple bean shown below:

Java


Note that I've used Java record getters' name convention in this example. In Java 14, such classes can be declared as records so necessary boilerplate code will be significantly reduced.

Let's add a Builder. The first step is quite traditional:

Java


Let's implement a traditional builder first so it will be more clear how Fluent Builder code is derived. A traditional Builder class would look like this:

Java


One important observation: every setter returns this and this in turn allows users of this call to invoke every method available in builder. This is the root of the issue, because a user is allowed to call the build() method prematurely, before all necessary fields are set.

In order to make Fluent Builder, we need to limit the possible choices to only allowed ones, therefore enforcing correct use of the builder. Since we're considering a case when all fields need to be set, then at every building step only one method is available. To do this, we can return dedicated interfaces instead of this and let Builder implement all these interfaces:

Java


Huh. Somewhat ugly and a lot of additional boilerplate. Can we do better? Let's try.

The first step is to stop implementing interfaces and instead return anonymous classes that implement these interfaces:

Java


This is much better. We again can safely return SimpleBeanBuilder from the builder() method since this class exposes only one method and does not allow users to build instances prematurely. But, much more importantly, we can omit whole setters and mutable fields boilerplate in the builder, which significantly reduces the amount of code. This is possible because we create anonymous classes in the scope where parameters of all setters are visible and accessible. We can use these parameters directly without need to store them!

The resulting code is comparable to the original Builder implementation in regard to total amount of code.

But that's not all. Since all anonymous classes are in fact implementation of the interfaces which contain only one method, we can replace anonymous classes with lambdas:

Java


Notice that the remaining SimpleBeanBuilder class is very similar to other builder interfaces, so we can replace it with lambda as well:

Java


Final touches:

  • Move interfaces inside SimpleBeanBuilder interface and do some renaming of interfaces. Since these interfaces extremely rare will appear in user code, we can use some standardized naming for them and simplify automated generation of the code. 
  • Rename setters, as there is no need to follow Java Bean naming convention because we have no getters here. 
  • The build() method is not necessary anymore. In original implementation it served as a signal that we're finished assembling POJO, but this is no longer necessary since once last field is set we have all necessary details to build POJO instance.

Below, is the full SimpleBean code after all these changes applied:

Java


Notice that there is very small amount of code which actually does something, most of the implementation is a bunch of interface declarations. Adding, changing or removing fields are quite simple to do, since very little code is involved.

For those who isn't yet used to deeply nested lambdas this code might be harder to get it at first look, but this is matter of experience. Also, there is no need to write such code manually as we can offload this task to IDE (just as we do with traditional builders). 

Using described above approach we can replace traditional Builders with Fluent Builders and get Builder convenience with Fluent API pattern safety.

Topics:
builder, design patterns, java, tutorial

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}