How to implement Builder pattern in Kotlin?

First and foremost, in most cases you don’t need to use builders in Kotlin because we have default and named arguments. This enables you to write

class Car(val model: String? = null, val year: Int = 0)

and use it like so:

val car = Car(model = "X")

If you absolutely want to use builders, here’s how you could do it:

Making the Builder a companion object doesn’t make sense because objects are singletons. Instead declare it as an nested class (which is static by default in Kotlin).

Move the properties to the constructor so the object can also be instantiated the regular way (make the constructor private if it shouldn’t) and use a secondary constructor that takes a builder and delegates to the primary constructor. The code will look as follow:

class Car( //add private constructor if necessary
        val model: String?,
        val year: Int
) {

    private constructor(builder: Builder) : this(builder.model, builder.year)

    class Builder {
        var model: String? = null
            private set

        var year: Int = 0
            private set

        fun model(model: String) = apply { this.model = model }

        fun year(year: Int) = apply { this.year = year }

        fun build() = Car(this)
    }
}

Usage: val car = Car.Builder().model("X").build()

This code can be shortened additionally by using a builder DSL:

class Car (
        val model: String?,
        val year: Int
) {

    private constructor(builder: Builder) : this(builder.model, builder.year)

    companion object {
        inline fun build(block: Builder.() -> Unit) = Builder().apply(block).build()
    }

    class Builder {
        var model: String? = null
        var year: Int = 0

        fun build() = Car(this)
    }
}

Usage: val car = Car.build { model = "X" }

If some values are required and don’t have default values, you need to put them in the constructor of the builder and also in the build method we just defined:

class Car (
        val model: String?,
        val year: Int,
        val required: String
) {

    private constructor(builder: Builder) : this(builder.model, builder.year, builder.required)

    companion object {
        inline fun build(required: String, block: Builder.() -> Unit) = Builder(required).apply(block).build()
    }

    class Builder(
            val required: String
    ) {
        var model: String? = null
        var year: Int = 0

        fun build() = Car(this)
    }
}

Usage: val car = Car.build(required = "requiredValue") { model = "X" }

Leave a Comment