Kotlin and the removal of the Builder Pa ...

Kotlin and the removal of the Builder Pattern from public life

Apr 16, 2024

Today, while searching, 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 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:

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

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:

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:

  1. Builder

  2. Constructor

  3. Getters

  4. Setters

  5. 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:

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:

And for the second example:

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://youtu.be/rTCjlyGVDGE

And the slides to support it:

https://www.slideshare.net/slideshows/fields-in-java-and-kotlin-and-what-to-expectpptx/266872293

https://www.scribd.com/presentation/715037407/Fields-in-Java-and-Kotlin-and-What-to-Expect

I hope you enjoyed this Coffee session and wish you all the best!

Enjoy this post?

Buy João Esperancinha a coffee

More from João Esperancinha