When Builder is anti-pattern

Sergiy Yevtushenko - Nov 5 '19 - - Dev Community

Some time ago I worked on the project which was full of "best practices", applied thoroughly everywhere. One of such "best practices" was mandatory use of Builder to create POJO's. Most of the POJO's boilerplate code was generated by IDE plugin, but it was only tiny portion of code. Actual problem was the use of these POJO's. Code was flooded with pieces like this:

    final MyPojo pojo = MyPojo.newBuilder()
                              .setThis(...)
                              .setThat(...)
                              .setSomethingElse(...)
                              ...
                              .build();
Enter fullscreen mode Exit fullscreen mode

Such an overuse of the builders has several negative effects:

  • code is unnecessarily verbose (I'd even say heavily noisy)
  • code is error prone

While first issue is more matter of taste, second one is not:

There is no way to tell at compile time if all necessary fields are set.

Plain all-args constructor or factory method works better in this case - compiler will make sure that I'll pass all parameters necessary to create POJO. How correct are these parameters it's the another story, but all of them definitely will be there. With builder I can easily omit one or two. Issue will appear later, at run time, when I get NPE.

The bright mind who introduced that practice in mentioned above project, decided that best way to solve the problem is to add validation into the build() method. It resulted that instead of getting NPE's in random locations, we started getting them inside build() methods. Nice improvement, isn't it?

The described above anti-pattern is an example of wider class of heavy design mistakes - shifting errors from compile time to run time. Such a shift is call for troubles of any size, from longer and more painful development to huge losses for those who uses the application.

So, what's the correct use of Builder?

My criteria of correct use of Builder pattern is extremely simple:
The build() method must produce complete instance. All necessary fields must be set to reasonable defaults if user did not provide values for them.

POJO's rarely fall into category of classes which can be built this way. Usually POJO has most or all fields necessary to represent information which just does not have reasonable default values.

Another place where Builder is often used are things like server or client configurations and usually Builder's are perfectly fine to this kind of applications.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .