At work, we have a sizeable RESTful service written in Java, and based on the Spring framework. It is not very complex – it mostly handles convenient access to our database. Nevertheless, it is a significant daily source of pain for me: it is poorly mantained and every new feature is patched hammring code into it.
After almost three years working on it, I registered that 90% of the problems we had were related to something
null that should not have been
null. After the n-th
NullPointerException, I snapped. I needed something better: I decided I would convert the entire project in Kotlin for that reason alone.
WarningI am lucky. At work I am the guy in charge of deciding the technical stack. Therefore, I can assume the risk of playing and spending time messing with the core application of our business. If you are not, it is always a bad idea to switch technology on an impulse. Really. Do your research first! Talk with who is in charge.
Why not just use
In recent years, Java got much better at almost everything. After years and years of stagnation, Java is now a not-unbearable language to work with. The addition of
Stream was excellent but the introduction of the
Optional<T> type is probably the best feature of any modern Java implementation.
Optional<T>, in short, wraps around a nullable variable providing type-safe access to that value. If you have
Optional<String> foo, you cannot use it where a
String is expected: you will first need to unwrap it and, as a consequence, check if the content of
foo is not null.
Optional types are not an exoteric feature anymore. If you know Kotlin, Rust, Swift, or any other language designed in this decade, you know what I am talking about.1
The feature is a life-saver, but, unfortunately, it is still a bit clunky. It is not completely integrated into the language and legacy libraries, and converting an old-style Java code into an Optional-Java code pollute your code of a lot of ugly
if (foo.isPresent()) checks.2
Kotlin, on the other hand, has been designed with this modern principle in mind and nullable/non-nullalbe types flow straightforwardly into the code. They are less verbose and they are widely supported. If you add to this the fact that the main Kotlin design principle is “painless Java interoperability”, you already know the best way to proceed.
Let’s begin by converting one class
At this point, I had a Java Spring codebase and I needed to configure my build system (Maven) to compile a Java/Kotlin hybrid Spring codebase. I knew it is possible, but I still felt fear and excitement.
Looking around, I found that IntelliJ contains a tool to add Kotlin’s support in your current project. You go in “Tool -> Kotlin -> Configure Kotlin in Project.” It was worth a try.
I clicked, IntelliJ started doing its magic and, after a while, Maven was configured for Kotlin. That was easy: I eagerly clicked Build and…
Unfortunately this automatic conversion does not work. At least in Maven and with my version of IntelliJ (2020.1). Somehow, the automation tool is broken and produces a Maven configuration that cannot compile Kotlin and Java code at the same time.
The good news is the solution was easy to find: I went to the official Kotlin documentation and copied the
maven-compiler-plugin plugin configuration from the example code an pasted in my
pom.xml. For your convenience, I copy the code below.
I tried again to compile, this time, everything worked. I was in business.
Add support for Spring
The first thing I did was to convert my Spring entities classes into Kotlin classes (and finally getting rid of all the annoying setter and getter). The built-in conversion tool is very good, even if the result is not really Kotlin-idiomatic. However, for simple classes, it works fine.
After I converted 4/5 classes, I try to compile again. Everything worked. I deployed the artifact to do some real test in the dev environment and… the app did not start. Oh, oh.
In the logs, there were errors like this:
Cannot subclass final class gr.helvia.hbf.core.domain.Tenant
I instantaneously knew the cause: classes in Kotlin are
final by default. They cannot be subclassed unless you mark them explicitely as
open. Apparently, Spring needs to subclass your classes to do its magic.
This was annoying: I should had go over all the Kotlin entities and add
open to their declaration. And what if other classes needed to be subclassed? Do I needed to repeat this try and error forever?
Luckily, there is yet another easy solution. You need to add the
allopen plugin. You can find the documentation here. This plugin will automatically and implicitly add
open to some specific
If you are using MongoDB the
spring configuration is not enough. You also need to open all the classes annotated with the
Document decorator. If you need so, you can copy my configuration:
And now the fun part!
After this point, the only part remaining is the fun part. I proceeded by converting class by class, starting from the more internal and simple one.
The beautiful thing is that you instantaneously get some benefits. For instance, I identified at least 10 potential
NullPointerExceptions in the Java implementation.
To spot them is easy: Kotlin is forced to null-assert (
!!) every nullable value and, by default, any object in Java is considered nullable. So every now and then, you spot a lot of
!! during object access:
foo!!.bar!!.gee. This visual clue lets you clearly see when a null access may occur and you can easily see where you should be more careful.
After this, I usually start to removing nullable types from functions that are not supposed to accept null parameters and enjoy see this effect propagate on the codebase.
After a lot of conversion/compilation/testing cycles, 52.9% of the original codebase is now in Kotlin. Except for the small issues reported here, the entire process was quite straightforward, and people working on the front-end never realized this change.
Now working on this application makes me happier, and I made it harder to make silly mistakes. I am pretty satisfied with the result.
Everyone but Go, of course. Go is doomed to repeat every single programming language design mistake of the last 50 years. I am sure that, at some point, Go will have its own non-nullable type. Meanwhile, you can keep
if (err != nil)-ing like we were in the 80s. ↩︎
isPresentcall is not the way of using
Optional, btw. We can consider it an antypattern. Optional should be used in a more “functional” way with
ifPresentmethod. But this is another story. ↩︎