Kotlin and the removal of the Builder Pattern from public life

Kotlin and the removal of the Builder Pattern from public life

To be more precise, the builder pattern hasn't been removed. We just don't code nor do we see it anymore and so it is like it doesn't exist. This post

Table of contents

I discovered code changes on the internet that were related to the typical Javax validations that soon will become Jakarta validations. In these code changes, I did notice that many people were fixing issues by changing validation annotations from, and just as an example, @Size to @field:Size. This of course comes after doing a migration from Java to Kotlin When I realized this I decided to have a closer look at these fixes.

One of the things that I've noticed that is quite bittersweet in Kotlin is that, when Kotlin created the Data classes, akin to Records in Java, it literally destroyed the whole concept of the Builder Pattern. If you don't know this, the Builder Pattern is something that has been slowly eroded and removed from the software landscape through the years. The first time I noticed that it was disappearing was during Lombok's inception back in 2009. Back then it was pretty clear that everyone was pretty much upset about making builders all the time, just to create class instances.

Since visualizing things is very important, especially these days, let's first take a look at the old way of creating classes. Say that we wanted to create a Book instance but we were only allowed to create a book with only any number of pages between 15 and 50. Jakarta's validations have one annotation called @Range which works very much like @Size in Javax. In the very old days, we would do something like this:

public class Book {

    @Range(min = 15, max = 50)
    private Long pages;

    public Book(Long pages) {
        this.pages = pages;
    }

    public Long getPages() {
        return pages;
    }

    public void setPages(Long pages) {
        this.pages = pages;
    }
}

And in order to create a Book with good practices, we would create a builder for it like this:

public class BookBuilder {

    private Long pages;

    public BookBuilder withPages(Long pages) {
        this.pages = pages;
        return this;
    }

    public Book build() {
        return new Book(pages);
    }
}

If you are already into Kotlin and have hardly ever seen a line of Java code, I can only imagine your reaction at the moment. Since I do not have any idea of how much you enjoy Kotlin or Java, let's compare this with the equivalent in Kotlin:

data class Book(
    @field:Range(min = 10, max = 30) val pages: Long? = 0
)

In Kotlin we are able to do a lot of things at once with extremely reduced lines of code. In 3 lines we have created a Builder, a constructor, a getter, a setter, and the book fields, not to mention the equals and hash functions. In fact, this is so reduced that this has already created some confusion in real life. Looking at one of the blogs where people found issues with this, like this one https://tedblob.com/kotlin-spring-validation/, we quickly realize that in these 3 lines of code there is much more to take into account than a few simple lines of code. When we declare a data class like this, we are defining many aspects of a class. In fact, we can say that a data class in Kotlin is a package of 5 things:

  • Builder

  • Constructor

  • Getters

  • Setters

  • Fields

In my field of work, I still come across situations where people just fall into a small trap. Myself included. The following does NOT work in Kotlin for the example I provided on GitHub:

data class BadBook(
    @Range(min = 10, max = 30) val pages: Long? = 0
)

Why is this? Well, you probably just saw the difference. It's about this @field, right? Unfortunately, this is inherently confusing for many people and we can't really say anything about this, because of how subtle this difference really is. And this small difference isn't really that difficult for me to understand, but, I attribute it to the fact that I have a good Java background. If you don't have this though, then visualizing this in our mental process can be a challenge. But again to the question, why doesn't this work? Intellij has a great tool to decompile Kotlin code into Java and if we decompile file Dao.kt, we will see the following code snippet. For the first example:

public final class Book {
    @Range(
            min = 10L,
            max = 30L
    )
    @Nullable
    private final Long pages;

    @Nullable
    public final Long getPages() {
        return this.pages;
    }
   (...)
}

And for the second example:

public final class BadBook {
    @Nullable
    private final Long pages;

    @Nullable
    public final Long getPages() {
        return this.pages;
    }

    public BadBook(@Range(min = 10L, max = 30L) @Nullable Long pages) {
        this.pages = pages;
    }
    (...)
}

So the reason why BadBook is bad is that @Range isn't being applied to the field. Instead, it is being applied to the constructor. This is why Kotlin in my opinion, has created not only @field but a range of other annotations, precisely because, by removing all 5 elements intrinsically related to the Builder pattern and bean instantiation from the code, Kotlin has also removed the place where they used to be programmed into. All of this, even though in the background nothing has really been removed.

This last bit about where the validation has gone can also be argued as not having anything to do with the Builder pattern. The Builder Pattern is only about the creation of objects and not really about where the annotations should be located right? Well, the point is that, with Kotlin, there is no need anymore to use setters, getters, or the typical withs, which used to be programmed in the Builder Pattern in order to create objects. By allowing to do so much by declaring just var / val fields, all of that became obsolete. And by making the Builder obsolete, so became the actual implicit implementation of some constructors. Of course, we can still do that, but Kotlin also allows us to override variables.

But you may ask now if am I protesting against Kotlin now. Not at all. I actually find it to be an amazing language. I just don't elevate it above Java and I don't see the point in criticizing a language for no reason. If Kotlin allows us to take shortcuts to reach our goals then it is also true that we end up not understanding a few things, or at least we run the risk to do so. In this case the Builder Pattern. This pattern is mostly the first thing people answer to the infamous question asked in job interviews: "Which design patterns do you know?". I guess in the near future, anything we may talk about the Builder Pattern may become completely irrelevant. In my view, Kotlin was able, with data classes, but also with just regular classes, to give a last kick goodbye to the Builder Pattern from the regular development world. But was this pattern any good for any of us while making software? I don't know 100% for sure, but I always found it to be annoying. Useful, but still annoying. Lombok did help with that, but we still had to use that @Builder annotation and frequently combined it with others. On the other hand, it is still a pattern that lives in the code, and it does help establish phases in the instance lifecycle that are very important when we want to serialize data or beans in the case of enterprise frameworks like Spring, Micronaut, or JEE. We have just demonstrated why is it so important to understand those phases.

As usual, I always leave an example of this on GitHub. Please have a look at it here: https://github.com/jesperancinha/jeorg-kotlin-test-drives/tree/main/jeorg-kotlin-masters/jeorg-kotlin-constructor.

Also don't miss out the video I created about other problems surrounding data classes over here:

https://dai.ly/x92l5oo


I have created documentation on this on Scribd:

Fields-in-Java-and-Kotlin-and-What-to-Expect

Fields in Java and Kotlin a... by João Esperancinha

also on slide-share: fields-in-java-and-kotlin-and-what-to-expect.

Fields in Java and Kotlin and what to expect.pptx from João Esperancinha

Thank you!

I hope you enjoyed this article as much as I did making it!
Please leave a review, comments or any feedback you want to give on any of the socials in the links bellow.
I’m very grateful if you want to help me make this article better.
I have placed all the source code of this application on GitHub.
Thank you for reading!