DEV Community

Sergiy Yevtushenko
Sergiy Yevtushenko

Posted on

Simple Implementation of Fluent Builder - Safe Alternative To Traditional Builder

The Builder pattern is extremely popular in Java applications. Unfortunately it's often misunderstood and incorrectly implemented and used which results to run time errors.

So, what to do in cases when object must be built completely and there are no safe defaults for fields (i.e. Builder pattern is not applicable)? In such cases would be very convenient to use Fluent Interface pattern which allows to avoid errors caused by missing field(s). Unfortunately proper implementation of Fluent Interface usually is too verbose to be practical so developers use plain Builder and try to rely on careful testing to avoid runtime errors.

Fortunately for most frequent use case when all fields for the object must be set, there is a simple solution which results to very concise and safe implementation of Builder with fluent interface, which I call Fluent Builder.

Lets assume that we want to create a Fluent Builder for simple bean shown below:

public class SimpleBean {
    private final int index;
    private final String name;

    private SimpleBean(final int index, final String name) {
        this.index = index;
        this.name = name;
    }

}

Enter fullscreen mode Exit fullscreen mode

In Java 14 such classes can be declared as records so necessary boilerplate code will be significantly reduced.

Lets add a Builder. First step is quite traditional:

...
    public static SimpleBeanBuilder builder() {
        return new SimpleBeanBuilder();
    }
...
Enter fullscreen mode Exit fullscreen mode

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

...
    private static class SimpleBeanBuilder {
        private int index;
        private String name;

        public SimpleBeanBuilder setIndex(final int index) {
            this.index = index;
            return this;
        }

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

        public SimpleBean build() {
            return new SimpleBean(index, name);
        }
    }
Enter fullscreen mode Exit fullscreen mode

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 user is allowed to call build() method prematurely, before all necessary fields are set.

In order to make Fluent Builder we need to limit possible choices to only allowed ones, therefore enforcing correct use of the builder. Since we're considering 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:

    ...
    public static SimpleBeanBuilder0 builder() {
        return new SimpleBeanBuilder();
    }
    ...

    private static class SimpleBeanBuilder implements SimpleBeanBuilder0, SimpleBeanBuilder1, SimpleBeanBuilder2 {
        private int index;
        private String name;

        public SimpleBeanBuilder1 setIndex(final int index) {
            this.index = index;
            return this;
        }

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

        public SimpleBean build() {
            return new SimpleBean(index, name);
        }

        public interface SimpleBeanBuilder0 {
            SimpleBeanBuilder1 setIndex(final int index);
        }

        public interface SimpleBeanBuilder1 {
            SimpleBeanBuilder2 setName(final String name);
        }

        public interface SimpleBeanBuilder2 {
            SimpleBean build();
        }
    }
Enter fullscreen mode Exit fullscreen mode

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

First step is to stop implementing interfaces and instead return anonymous classes which implement these interfaces:

    public static SimpleBeanBuilder builder() {
        return new SimpleBeanBuilder();
    }
    ...
    ...
    private static class SimpleBeanBuilder {
        public SimpleBeanBuilder1 setIndex(int index) {
            return new SimpleBeanBuilder1() {
                @Override
                public SimpleBeanBuilder2 setName(final String name) {
                    return new SimpleBeanBuilder2() {
                        @Override
                        public SimpleBean build() {
                            return new SimpleBean(index, name);
                        }
                    };
                }
            };
        }

        public interface SimpleBeanBuilder1 {
            SimpleBeanBuilder2 setName(final String name);
        }

        public interface SimpleBeanBuilder2 {
            SimpleBean build();
        }
    }
Enter fullscreen mode Exit fullscreen mode

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

Resulting code looks is comparable to original Builder implementation in regard to total amount of code.

But we can do better! Since all anonymous classes are in fact implementation of the interfaces which contain only one method, we can replace anonymous classes with lambdas:

    private static class SimpleBeanBuilder {
        public SimpleBeanBuilder1 setIndex(int index) {
            return name -> () -> new SimpleBean(index, name);
        }

        public interface SimpleBeanBuilder1 {
            SimpleBeanBuilder2 setName(final String name);
        }

        public interface SimpleBeanBuilder2 {
            SimpleBean build();
        }
    }
Enter fullscreen mode Exit fullscreen mode

Resulting Fluent Builder contains even less boilerplate code than original builder!

But we can do even better! Notice that remaining SimpleBeanBuilder class is very similar to other builder interfaces, so we can replace it with lambda as well:

    public static SimpleBeanBuilder builder() {
        return index -> name -> () -> new SimpleBean(index, name);
    }

    public interface SimpleBeanBuilder {
        SimpleBeanBuilder1 setIndex(int index);
    }

    public interface SimpleBeanBuilder1 {
        SimpleBeanBuilder2 setName(final String name);
    }

    public interface SimpleBeanBuilder2 {
        SimpleBean build();
    }
Enter fullscreen mode Exit fullscreen mode

For those who isn't yet used to deeply nested lambdas this code might be harder to read. From the other hand, there is no need to write it manually as we can offload this task to IDE (just as we do with traditional builders).

Using this approach we can replace traditional Builders with Fluent Builders and get Builder convenience with Fluent Interface safety.

Latest comments (4)

Collapse
 
parthasutradhar profile image
Partha Sutradhar

Hi Sergiy, Is this related to Step Builder?

Collapse
 
siy profile image
Sergiy Yevtushenko

I think this is another name for Fluent Builder.

Collapse
 
rmwthorne profile image
Ross Thorne

Thank you for writing an interesting article! Especially on Java, there is not a lot of Java content on dev!
I have a question though, if the point of the interfaces here is to ensure that all the class fields are correctly set before build() is called, can you not just perform those checks in build()? Is it to force the check for this at compile time rather than run time?

Collapse
 
siy profile image
Sergiy Yevtushenko • Edited

The main idea is to shift checks back to compile time.

In general I'm trying to collect and organize new Java coding practices which will improve both development performance and code quality. The idea is to use various approaches to reduce mental overhead during development and let compiler help us write correct code. Mostly ideas are borrowed from functional programming, but sometimes this is just a conventions (for example like null handling approach described here) or interesting findings like one described in this article.