Kotlin-ifying a Builder Pattern

How a few Kotlin wrapper methods can transform your builder pattern into a Kotlin DSL

Doug Sigelbaum
Google Developers
3 min readJun 28, 2018

--

The builder pattern has proven to be a useful paradigm in the Java programming language when there are many parameters needed to construct an object. As Effective Java points out, constructors or factory methods with too many parameters become susceptible to bugs when parameters are accidentally swapped in clients. Kotlin’s named parameters solve this problem in many cases because a Kotlin caller can specify the name of every parameter, reducing the chance of misplacing one. However, since Java clients cannot leverage named parameters, the builder pattern remains helpful. Additionally, dynamically setting optional parameters can be useful, also only possible with the builder pattern.

Let’s consider a simple builder pattern in the Java Programming Language. We first have a POJO Company class that contains several attributes, perhaps enough to constitute using builders:

A Company has a list of Employees and a list of Offices. Those classes also use the builder pattern:

And in Office.java:

Now, if we wanted to build a Company containing one Employee and one Office caller, we could do the following from Java:

In a Kotlin client, we would have similar code:

Kotlin wrapper methods with Lambda Parameters

In Dokka, we use kotlinx.html, a beautiful DSL for building objects representing HTML. Anko Layouts does something similar for programmatically building layouts in Android. As discussed in my previous post, slice-builders-ktx also provides a DSL wrapper on top of a builder pattern. All of these libraries use lambda parameters to give clients a DSL look and feel. Lambda parameters are available in Kotlin and Java 8+, though they work slightly differently. Since many of you, especially Android Developers, are using Java 7, we will only look at Kotlin lambda parameters in this post. Let’s now explore creating a DSL for Company!

Top Level Wrapper

Here is a Kotlin function that will be the only top-level function in our Company DSL:

Note: we mark the function as inline to remove the lambda overhead. You can learn more about `inline` here.

Since the lambda parameter is labeled type Company.Builder.() -> Unit, all statements in the lambda have the scope of the Company.Builder being built. Now, a Kotlin client can use the company function to avoid directly instantiating Company.Builder and calling build():

Wrapping Nested Builders

We can now add a couple more extension functions for Company.Builder to avoid ever directly instantiating or adding Employee.Builders or Office.Builders to the parent Company.Builder. Here is a potential solution:

Using these extension functions, an equivalent Kotlin client could then be:

Almost done! We have accomplished our goal in prettifying the builder API, but we have introduced a new issue. Check out the following client:

Unfortunately, this compiles and runs despite the nested Employee! In Kotlin, your scope can span multiple objects at the same time. This means, anything inside the company { … } lambda has access to the Company.Builder. So, anything inside the nested employee { … } lambda has access to the Employee.Builder and the Company.Builder. This code is therefore linearly adding 2 employees “Doug” and “Sean” to the Company with no direct relationship between Doug and Sean.

How can we modify our extension functions to cause a compiler error when a client accesses the wrong scope, as in the above example? In other words, how can we make our DSL type-safe? Luckily, Kotlin 1.1 introduced the DslMarker annotation class to solve this problem.

Make your DSL type-safe using DslMarker

Let’s first create an annotation class annotated with DslMarker:

Now, if a set of classes is annotated with @CompanyDsl, callers will not have implicit access to multiple receivers whose classes are in the set of annotated classes. Callers will instead only have implicit access to the receiver with the nearest scope.

DslMarker is in Kotlin stdlib, so there is a chance your original builder classes do not have that dependency, and you therefore can’t annotate your Builder classes directly. If that is the case, you can subclass the builders and use those subclasses in your Kotlin wrapper methods:

Now, the previous client example will get the compiler error we wanted:

…can’t be called in this context by implicit receiver. Use the explicit one if necessary.

Good, we’re done!

Takeaway

TL;DR: To give your builder API a DSL feel in Kotlin, you can write a thin functional layer consisting mainly of extension factory methods with extension function parameters, all without modifying your original library. Don’t forget to use @DslMarker to keep your DSL type-safe!

--

--