232

Hi I am a newbie in the Kotlin world. I like what I see so far and started to think to convert some of our libraries we use in our application from Java to Kotlin.

These libraries are full of Pojos with setters, getters and Builder classes. Now I have googled to find what is the best way to implement Builders in Kotlin but no success.

2nd Update: The question is how to write a Builder design-pattern for a simple pojo with some parameters in Kotlin? The code below is my attempt by writing java code and then using the eclipse-kotlin-plugin to convert to Kotlin.

class Car private constructor(builder:Car.Builder) {
    var model:String? = null
    var year:Int = 0
    init {
        this.model = builder.model
        this.year = builder.year
    }
    companion object Builder {
        var model:String? = null
        private set

        var year:Int = 0
        private set

        fun model(model:String):Builder {
            this.model = model
            return this
        }
        fun year(year:Int):Builder {
            this.year = year
            return this
        }
        fun build():Car {
            val car = Car(this)
            return car
        }
    }
}
7
  • 1
    do you need model and year to be mutable? Do you change them after a Car creation? Commented Mar 22, 2016 at 4:48
  • I guess they should be immutable yes. Also you want to be sure they are set both and not empty Commented Mar 22, 2016 at 6:02
  • 1
    You can also use this github.com/jffiorillo/jvmbuilder Annotation Processor to generate the builder class automatically for you. Commented Aug 20, 2018 at 7:48
  • @JoseF Good idea to add it to standard kotlin. It is useful for libraries written in kotlin. Commented Aug 20, 2018 at 12:49
  • 2
    Most of the answers miss one basic but important use of a builder, that is to build an immutable object incrementally. There are myriad uses of this, for example, when parsing an input. Creating a new data class for each event would be completely wasteful in that case. Commented Nov 8, 2020 at 23:34

18 Answers 18

431

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" }

Sign up to request clarification or add additional context in comments.

19 Comments

Nothing, but the author of the question specifically asked how to implement the builder pattern.
I should correct myself, the builder pattern has some advantages, e.g. you could pass a partially constructed builder to another method. But you're right, I'll add a remark.
@KirillRakhman how about calling the builder from java? Is there an easy way to make the builder available to java?
All three versions can be called from Java like so: Car.Builder builder = new Car.Builder();. However only the first version has a fluent interface so the calls to the second and third versions can't be chained.
I think the kotlin example at the top only explains one possible use case. The main reason I use builders is to convert a mutable object into an immutable one. That is, I need to mutate it over time while I'm "building" and then come up with an immutable object. At least in my code there are only one or 2 examples of code that has so many variations of parameters that I would use a builder instead of several different constructors. But to make an immutable object, I have a few cases where a builder is definitely the cleanest way I can think of.
|
83

One approach is to do something like the following:

class Car(
  val model: String?,
  val color: String?,
  val type: String?) {

    data class Builder(
      var model: String? = null,
      var color: String? = null,
      var type: String? = null) {

        fun model(model: String) = apply { this.model = model }
        fun color(color: String) = apply { this.color = color }
        fun type(type: String) = apply { this.type = type }
        fun build() = Car(model, color, type)
    }
}

Usage sample:

val car = Car.Builder()
  .model("Ford Focus")
  .color("Black")
  .type("Type")
  .build()

3 Comments

Thanks a lot! You made my day! Your answer should be marked as SOLUTION.
But why? This is just not necessary in Kotlin, bloated, not null-safe and error prone. :( You could even do some validation by just providing an init {} block. Please don't force outdated Java patterns into Kotlin.
Because you might need to instantiate the Car class in Java code.
12

I personally have never seen a builder in Kotlin, but maybe it is just me.

All validation one needs happens in the init block:

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

    init {
        if(year < 1900) throw Exception("...")
    }
}

Here I took a liberty to guess that you don't really wanted model and year to be changeable. Also those default values seems to have no sense, (especially null for name) but I left one for demonstration purposes.

An Opinion: The builder pattern used in Java as a mean to live without named parameters. In languages with named parameters (like Kotlin or Python) it is a good practice to have constructors with long lists of (maybe optional) parameters.

7 Comments

Thanks a lot for the answer. I like your approach but the downside is for a class with many parameters it becomes not so friendly to use the constructor and also test the class.
+Keyhan two other ways you can do validation, assuming the validation doesn't happen between the fields: 1) use property delegates where the setter does validation - this is pretty much the same thing as having a normal setter that does validation 2) Avoid primitive obsession and create new types to pass in that validate themselves.
@Keyhan this is a classic approach in Python, it works very well even for functions with tens of arguments. The trick here is to use named arguments (not available in Java!)
Yes, it is also a solution worth using, it seems unlike java where builder class have some clear advantages, in Kotlin it is not so obvious, talked to C# developers, C# also have kotlin like features (default value and you could name params when calling constructor) they did not use builder pattern either.
@vxh.viet many of such cases can be solved with @JvmOverloads kotlinlang.org/docs/reference/…
|
9

Because I'm using Jackson library for parsing objects from JSON, I need to have an empty constructor and I can't have optional fields. Also all fields have to be mutable. Then I can use this nice syntax which does the same thing as Builder pattern:

val car = Car().apply{ model = "Ford"; year = 2000 }

5 Comments

In Jackson you don't actually need to have an empty constructor, and fields don't need to be mutable. You just have to annotate your constructor parameters with @JsonProperty
You don't even have to annotate with @JsonProperty anymore, if you compile with the -parameters switch.
Jackson can actually be configured to use a builder.
If you add the jackson-module-kotlin module to your project, you can just use data classes and it will work.
How is this doing the same thing as a Builder Pattern? You are instantiating the final product and then swapping out / adding information. The whole point of the Builder pattern is to not be able to get the final product until all the necessary information is present. Removing the .apply() leaves you with an undefined car. Removing all the constructor arguments from Builder leaves you with a Car Builder, and if you try to build it into a car, you'll likely run into an exception for not having specified the model and year yet. They are not the same thing.
8

I have seen many examples that declare extra funs as builders. I personally like this approach. Save effort to write builders.

package android.zeroarst.lab.koltinlab

import kotlin.properties.Delegates

class Lab {
    companion object {
        @JvmStatic fun main(args: Array<String>) {

            val roy = Person {
                name = "Roy"
                age = 33
                height = 173
                single = true
                car {
                    brand = "Tesla"
                    model = "Model X"
                    year = 2017
                }
                car {
                    brand = "Tesla"
                    model = "Model S"
                    year = 2018
                }
            }

            println(roy)
        }

        class Person() {
            constructor(init: Person.() -> Unit) : this() {
                this.init()
            }

            var name: String by Delegates.notNull()
            var age: Int by Delegates.notNull()
            var height: Int by Delegates.notNull()
            var single: Boolean by Delegates.notNull()
            val cars: MutableList<Car> by lazy { arrayListOf<Car>() }

            override fun toString(): String {
                return "name=$name, age=$age, " +
                        "height=$height, " +
                        "single=${when (single) {
                            true -> "looking for a girl friend T___T"
                            false -> "Happy!!"
                        }}\nCars: $cars"
            }
        }

        class Car() {

            var brand: String by Delegates.notNull()
            var model: String by Delegates.notNull()
            var year: Int by Delegates.notNull()

            override fun toString(): String {
                return "(brand=$brand, model=$model, year=$year)"
            }
        }

        fun Person.car(init: Car.() -> Unit): Unit {
            cars.add(Car().apply(init))
        }

    }
}

I have not yet found a way that can force some fields to be initialized in DSL like showing errors instead of throwing exceptions. Let me know if anyone knows.

Comments

3

For a simple class you don't need a separate builder. You can make use of optional constructor arguments as Kirill Rakhman described.

If you have more complex class then Kotlin provides a way to create Groovy style Builders/DSL:

Type-Safe Builders

Here is an example:

Github Example - Builder / Assembler

1 Comment

Thanks, but I was thinking of using it from java as well. As far as I know optional arguments would not work from java.
2

People nowdays should check Kotlin's Type-Safe Builders.

Using said way of object creation will look something like this:

html {
    head {
        title {+"XML encoding with Kotlin"}
    }
    // ...
}

A nice 'in-action' usage example is the vaadin-on-kotlin framework, which utilizes typesafe builders to assemble views and components.

Comments

1

I would say the pattern and implementation stays pretty much the same in Kotlin. You can sometimes skip it thanks to default values, but for more complicated object creation, builders are still a useful tool that can't be omitted.

2 Comments

As far as constructors with default values you can even do validation of input using initializer blocks. However, if you need something stateful (so that you don't have to specify everything up front) then the builder pattern is still the way to go.
Could you give me a simple example with code? Say a simple User class with name and email field with validation for email.
1

I was working on a Kotlin project that exposed an API consumed by Java clients (which can't take advantage of the Kotlin language constructs). We had to add builders to make them usable in Java, so I created an @Builder annotation: https://github.com/ThinkingLogic/kotlin-builder-annotation - it's basically a replacement for the Lombok @Builder annotation for Kotlin.

Comments

1

I am late to the party. I also encountered the same dilemma if I had to use Builder pattern in the project. Later, after research I have realized it is absolutely unnecessary since Kotlin already provides the named arguments and default arguments.

If you really need to implement, Kirill Rakhman's answer is solid answer on how to implement in most effective way. Another thing you may find it useful is https://www.baeldung.com/kotlin-builder-pattern you can compare and contrast with Java and Kotlin on their implementation

Comments

1

A little changed and improved version of answers above

class MyDialog {
  private var title: String? = null
  private var content: String? = null
  private var confirmButtonTitle: String? = null
  private var rejectButtonTitle: String? = null

  @DrawableRes
  private var icon: Int? = null


  fun show() {
    // set dialog content here and show at the end
  }

  class Builder {
      private var dialog: MyDialog = MyDialog()

      fun title(title: String) = apply { dialog.title = title }

      fun icon(@DrawableRes icon: Int) = apply { dialog.icon = icon }

      fun content(content: String) = apply { dialog.content = content }

      fun confirmTitle(confirmTitle: String) = apply { dialog.confirmButtonTitle = confirmTitle }

      fun rejectButtonTitle(rejectButtonTitle: String) = apply { dialog.rejectButtonTitle = rejectButtonTitle }

      fun build() = dialog
  }
}

And usage

MyDialog.Builder()
        .title("My Title")
        .content("My content here")
        .icon(R.drawable.bg_edittext)
        .confirmTitle("Accept")
        .rejectButtonTitle("Cancel")
        .build()
        .show()

1 Comment

My dude, your code doesn't work, you are returning freshly created instance of MyDialog in build() method :)
1

The only thing I have not seen mentioned here is the fact that the builder functionality is completely covered by the combination of the already mentioned default values and the .copy() method.

The only use case that the presence of named parameters with default values doesn't cover is the one where you might want to populate the builder gradually with data that you receive asynchronously and actually build your class at some later point once you have all the ingredients. Kotlin enables this by virtue of the .copy() method implemented by all data classes letting you build your class gradually without much hassle and zero boilerplate, leveraging the power of named parameters.

No need to use builders in Kotlin at all. You will need to continue using/understanding them as they are used in your Java dependencies but they should start becoming a thing of the past.

2 Comments

The result should be also accessible from Java. Is this so?
i am not sure how default values and the default methods of data classes get translated into java code. usually I use java classes in my kotlin files and not the other way around. but as seen from this link gist.github.com/kewp/1efc1a4c406577342c43ccb258bf8739 a bunch of methods are autogenerated that give you the functionality of a builder... among others
0
class Foo private constructor(@DrawableRes requiredImageRes: Int, optionalTitle: String?) {

    @DrawableRes
    @get:DrawableRes
    val requiredImageRes: Int

    val optionalTitle: String?

    init {
        this.requiredImageRes = requiredImageRes
        this.requiredImageRes = optionalTitle
    }

    class Builder {

        @DrawableRes
        private var requiredImageRes: Int = -1

        private var optionalTitle: String? = null

        fun requiredImageRes(@DrawableRes imageRes: Int): Builder {
            this.intent = intent
            return this
        } 

        fun optionalTitle(title: String): Builder {
            this.optionalTitle = title
            return this
        }

        fun build(): Foo {
            if(requiredImageRes == -1) {
                throw IllegalStateException("No image res provided")
            }
            return Foo(this.requiredImageRes, this.optionalTitle)
        }

    }

}

Comments

0

I implemented a basic Builder pattern in Kotlin with the follow code:

data class DialogMessage(
        var title: String = "",
        var message: String = ""
) {


    class Builder( context: Context){


        private var context: Context = context
        private var title: String = ""
        private var message: String = ""

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

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

        fun build() = KeyoDialogMessage(
                title,
                message
        )

    }

    private lateinit var  dialog : Dialog

    fun show(){
        this.dialog= Dialog(context)
        .
        .
        .
        dialog.show()

    }

    fun hide(){
        if( this.dialog != null){
            this.dialog.dismiss()
        }
    }
}

And finally

Java:

new DialogMessage.Builder( context )
       .title("Title")
       .message("Message")
       .build()
       .show();

Kotlin:

DialogMessage.Builder( context )
       .title("Title")
       .message("")
       .build()
       .show()

Comments

0
class Person(
    val name:String,
    val family:String,
    val age:Int,
    val nationalCode: String?,
    val email: String?,
    val phoneNumber: String?
) {

    // Private constructor
    private constructor(builder: Builder) : this (
        builder.name,
        builder.family,
        builder.age,
        builder.nationalCode,
        builder.email,
        builder.phoneNumber
    )

    // Builder class

    // 1 Necessary parameters in Builder class : name , family
    class Builder(val name :String,val family :String) {

        // 2 Optional parameters in Builder class :
        var age: Int = 0
            private set
        var nationalCode: String? = null
            private set
        var email: String? = null
            private set
        var phoneNumber: String? = null
            private set

        fun age(age: Int) = apply { this.age = age }
        fun nationalCode(nationalCode: String) =
            apply { this.nationalCode = nationalCode }
        fun email(email: String) = apply { this.email = email }
        fun phoneNumber(phoneNumber: String) =
            apply { this.phoneNumber = phoneNumber }

        // 3 Create
        fun create() = Person(this)

    }
}

for accessing :

val firstPerson = Person.Builder(
    name = "Adnan",
    family = "Abdollah Zaki")
    .age(32)
    .email("[email protected]")
    .phoneNumber("+989333030XXX")
    .nationalCode("04400XXXXX")
    .create()

val secondPerson = Person.Builder(
    name = "Foroogh",
    family = "Varmazyar")
    .create()

1 Comment

This works in Java too?
0

I just found a fun way to create builder in kotlin:

enter image description here

As you can see, moduleBuilder can be reuse for other grafana build.

Here is the code:

class Grafana(
    private val module: String,
    private val scene: String,
    private val action: String,
    private val metric: String
) {
    companion object {
        fun build(module: String, scene: String, action: String, metric: String) =
            Grafana(module, scene, action, metric)

        val builder = ::build.curriedBuilder()

        private fun <P1, P2, P3, P4, R> Function4<P1, P2, P3, P4, R>.curriedBuilder() =
            fun(p1: P1) = fun(p2: P2) = fun(p3: P3) = fun(p4: P4) = this(p1, p2, p3, p4)
    }

    fun report() = Unit
}


val moduleBuilder = Grafana.builder("module")
val scene = moduleBuilder("scene")
val gfA = scene("action")("metric")
gfA.report()

val sceneB = moduleBuilder("sceneB")
val gfB = sceneB("action")("metric")
gfB.report()

val gfC = Grafana.builder("xx")("xxx")("xxxx")("xxxx")
gfC.report()

Comments

0
data class Car(
    val model: String,// NonNull
    val color: String?,// Nullable
    val type: String// NonNull
) {
    init {
        // do check between variables 
        if (color != "black" && color != "white") {
            throw Exception()
        }
    }
}

// usage:

   val car =  Car(....)

Comments

-2

you can use optional parameter in kotlin example:

fun myFunc(p1: String, p2: Int = -1, p3: Long = -1, p4: String = "default") {
    System.out.printf("parameter %s %d %d %s\n", p1, p2, p3, p4)
}

then

myFunc("a")
myFunc("a", 1)
myFunc("a", 1, 2)
myFunc("a", 1, 2, "b")

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.