After two years of being in the awesome position of being able to use a 'new' language in my day to day work, I felt it was a good moment to look back at this time and consider what I learned from it, and how I feel about using Kotlin now.
Introduction
In 2018 I wrote a blog post on Kotlin explaining how much a fan I was. After two more years where I also did 100% of my work in Kotlin, my belief that Kotlin has a great future in the Java ecosystem has only grown stronger.
I started working for Bol.com, the largest e-commerce platform here in Holland, in Jan 2019. Kotlin had already been adopted there as an officially supported language when I started and Spring Boot starters had been made available internally. A strong and growing Kotlin community was already in place in the company.
As with any change, it is often met with some concerns and objections, which I want to address in this post.
Hiring Talent
At the end of 2017 I was working for a start-up where a group of developers, myself included, wanted to introduce Kotlin as an alternative to Java. Even though we had solid arguments in favor of allowing devs to use Kotlin, the primary concern with management was the availability of "Kotlin Developers". Eventually that was the deciding factor for us to not get a go-head to support Kotlin.
Looking back at the last two years however, I feel that this argument is so deeply flawed I’d go as far as that the argument is actually a massive foot-gun: if hiring talent is a concern, not allowing Kotlin is probably a bad idea.
First of all; when I started out in the Cerberus team I was the only person with industry experience in Kotlin. New developers in our team got up to speed in Kotlin within two weeks. Sure there were a lot of moments initially in reviews where we could suggest minor changes, but since Kotlin is more or less a Java dialect, there were no big changes that needed to be made. Just some pointers on how Kotlin often had more convenient ways of doing things were enough.
Secondly; I strongly feel that allowing Kotlin gives companies a competitive advantage over companies that don’t. Many very talented developers are eager to try out new things. Especially when it concerns Kotlin; a lot of Java developers already have been exposed to Kotlin (it’s a massive recurring theme at most Java conferences) and most would like to try. When developers look for another job, aside from salary, being allowed 'new' things might be just the thing that lets them decide to work for you, over your competitor.
Productivity
But with Java developers learning a new language, is productivity an issue? No, not at all. Like I said in the previous section, literally every developer we got in our team that was new to Kotlin was at least as productive in Kotlin as they were in Java. Most importantly; Kotlin is simply a more productive language than Java is.
For me it’s similar to using Lombok in Java projects. I don’t want to have to deal with creating getters and setters for Object, I want to focus on the hard parts of writing software. For everything else; if there are tools that handle the boilerplate for me; great. Kotlin is probably as boilerplate-free as a statically typed language there is.
Null-safety
However the killer feature for Kotlin still is that the null-safety is baked into its type system. We developers love to argue about languages, but I strongly believe it’s impossible to argue in good faith that a null safe type system is a bad idea. Sir Antony Hoare called it his Billion Dollar Mistake for a reason!
Kotlin saved our behinds a couple of times by simply forcing us to handle nulls correctly. In one situation a JSON response had a null field where we did not expect it; the code immediately threw an exception when trying to deserialize it to a class where the field cannot be null.
Another situation involved the refactoring of a field where the end-user wanted it to be optional; changing a simple LocalDate to LocalDate? showed us exactly where in our code we depended on a value being present.
Not only does Kotlin force you to handle the absence of values, it does so in an incredibly elegant fashion. For example because it’s part of the type system (Any and Any?) you can force a generic to not be null, or on the other hand allow it. Null-safe calls and the elvis operator let you chain calls on a potential 'null' value and then eventually return a default (or throw an exception).
I would love for Java to get the same capabilities, but unfortunately this is impossible without it breaking backward-compatibility. In Java nullable is the default, in Kotlin not-null is. So at best Java can accomplish is more syntactic sugar on top of optional, which has its own issues, such as it just being a generic wrapper in a language based on type erasure.
Data classes
Another great strength I want to mention are Data classes. Data classes are simply amazing. I’m incredibly glad Java is finally getting records, but they won’t be nearly as powerful as data classes are, at least not until Java also gets destructuring.
I won’t really go into Data classes too much (after all, they’re so much a no-brainer they’re coming to Java), but I specifically want to mention them that I would strongly urge you to consider using Kotlin in your Java projects even if it is to get rid of Lombok. With Kotlin data classes there is in my opinion not really a reason to still use Lombok.
Type inference & Immutability
Fortunately Java now has 'var'. Why there is no corresponding 'val' I will never understand however. Immutability should be the default, and in production Kotlin code 'var' is simply incredibly rare. I personally don’t really care if you name it 'const', or 'let': just let us have a special keyword for immutable variables.
While Java fortunately has local type inference, Kotlin takes it a step further and allows you to infer types on many other levels like class members and method return types.
This brings me to one pitfall; I personally feel type inference in method return types should be used sparingly. An example:
fun someFunction() = someOtherFunction()
The short-hand function definition is great in my opinion, but there’s a potential problem here! When the return type of someOtherFunction
changes, so does the return type of someFunction
. So in our team we had a rule that functions should generally have an explicit return type:
fun someFunction(): Set<String> = someOtherFunction()
This way if the return type of someOtherFunction
changes in a refactoring, you will get a compile-error at the location of the problem and not somewhere else.
A common argument I hear against type inference is that it makes code hard to read. In theory it could, but frankly after a few years of Kotlin I tend to feel that type inference actually forces proper naming of variables. Keep in mind that Java code typically looks not like this:
List<Person> list = somePersonService.findByName("John");
doSomethingWith(list);
But more like:
List<Person> list = somePersonService.findByName("John");
// 20
// lines
// of
// other
// code
doSomethingWith(list);
Not having type inference does not make the above code readable, properly naming 'list' as 'persons' for example does. In most cases you’d have to look quite a bit above where the variable is used to 'see' the type. So not using type inference is a crutch at best, and an excuse to not write clean code at worst.
Extension functions
Extension functions are another great feature of Kotlin. And like type inference, it can come with a downside. It’s a very powerful tool, and here the 'Peter Parker Principle' applies: with great power comes great responsibility!
An example of where this can make your life easier is by extending ResultSet:
fun ResultSet.getUUID(column: String) : UUID = UUID.fromString(getString(column))
Just a convenience function we used all over the place simply because we used UUIDs for primary IDs in our database and ResultSet does not support them natively. These kinds of extension methods would be done in Java by statically importing UUID.fromString
(which can give naming clashes) or by statically importing a utility function. In Kotlin we can map rows like this:
private val someMapper = rowMapper { rs ->
SomeEntity(
rs.getUUID("id"),
rs.getUUID("parent_id"),
rs.getString("name"),
//..
)
}
There is a pitfall however. Extension utility functions like the above are fine to be public; you’re going to use them almost everywhere. But it’s important to be careful with extension functions with specific uses and making them private.
As an example, Spring REST services often have a lot of mappings between serialization DTO’s, business domain objects and database entities. A cleanly structured service separates these layers, with no layers having a dependency to the layer 'above'.
So in our controller layer, we often have extension methods on the domain objects, for example a Person domain object might have an extension method like this:
private fun Person.toDto() = PersonDTO(firstName, lastName, email)
Note
|
What’s nice about extension functions is that the 'this' scope is the object you’re working on. So 'name', 'lastName' etc are the members of the Person object you put the extension on. |
First of all; in the example above is where using a function short-hand with type inference is perfectly fine; it’s clear it’s returning a PersonDTO and there’s no way the signature is going to change. It’s not calling another function.
More importantly; it’s declared private. It still allows you to use extension functions, without letting them be used in other places.
Quality of life
I have touched on this in my previous blog post as well, but while Kotlin has a few 'big' features, it has many MANY more 'small' ones. Too many to all mention here, but you really notice it when you move back to a project based on Java. Why doesn’t Java have an notEmpty() next to empty()? Why no convenient .map function on collections? I mean, by far the most mapping we do is from no collection to another. Why no simple .toSet() on collections?
A big one here is the support of reified generics Kotlin offers (limited to inline functions however). There are a lot of situations where you want to create reusable utility functions for generics. Like say, mapping JSON to a class, without having to pass in the class itself all the time. You don’t really see how nice it is, when you go back to Java and have to do without.
Operator overloading is another great example. While I do understand Java’s choice to not offer it seeing the mess they made of the standard C++ API, but darn, is it convenient to have a few fixed operators like get ([]) or plusAssign (+=) you can override. One of our projects was a rule engine where we created quite a beautiful well maintainable DSL without a lot of overhead.
There’s a lot more examples: string interpolation, properties, default and named arguments, smart casts, if, try, when etc being expressions instead of statements. All of these I sorely miss when I have to write Java again.
Conclusion
As they say, Kotlin developers have all the fun
. Over the last years my opinion on the subject of Java versus Kotlin has only drifted more towards Kotlin. When I started using Kotlin and considering whether it should be used in production I most certainly was sensitive to the "what about hiring developers" argument.
As of the last year, I still consider it a valid argument, but in reverse. "Can we hire developers if we don’t allow Kotlin?" is in my opinion at least as valid a question. I do understand the fear; I have seen the damage done by a few overenthusiastic developers writing JavaScript or Go services in a Java based organisation.
For me Kotlin is not a separate language at all; it’s a Java dialect. The way you program is the same, it just gives you a lot more tools and conveniences. It also removes a lot of the warts and boilerplate from Java that don’t really add anything. Java developers in general feel right at home, much more so than with for example Scala or Groovy.
So the question really is; can companies not allow Kotlin and remain competitive? It’s not a risk I would be willing to take.