Kotlin - The Good, the Bad and the Ugly
Tags: kotlin, java
Published: 2018-09-21 11:31:25

---
title: Kotlin - The Good, the Bad and the Ugly
published: true
description: A discussion on the cool & not-so-cool features of the Kotlin programming language.
tags: Kotlin, Java
---

Hi folks!

This is an article I've wanted to write for quite some time now. I've seen my fair share of [Kotlin](https://kotlinlang.org/) and also used it in production. Kotlin is a JVM-based language developed by Jetbrains, known for their IDEs and developer tools. Kotlin is very interesting from a language perspective, it has many merits, but also some pitfalls and awkward design choices. Let's get right to it!

# The Good

I could fill entire books about the things I like about Kotlin. Here is a run-down of higlights.

## Semicolons are optional

Not "optional" as in "well most of the time it works without them, just sometimes it makes a difference" but as in "use them or drop them, the program won't change". Less clutter in code, and my wrist is really happy about this.

## NULL is a separate type

`String` (non-`null` String) and `String?` (nullable String) are two distinct types in Kotlin. If you stick to Kotlin methodology, your programs are unable to throw the dreaded `NullPointerException`s at you.

## Extension functions

Did you ever want a `null`-safe variant of `myStringVariable.isNullOrEmpty()` in Java? Well, Kotlin has you covered! You can write static methods (with certain signatures) and call them as if they were regular member methods. This also marks the death of the dreaded `StringUtils` class full of static methods. All of these can be extension methods now!

## Flow Typing & Type Inference

For all local variables, you just use `var` and the compiler will figure out the type on its own. Java 10 has that too, but with Kotlin's stronger type system it's even more valuable. Kotlin also does not require a cast operator. If you check that an object is `instanceOf` some class, then inside that `if` block, the variable will be treated as such, without you having to downcast it manually. Kotlin calls it "smart casts".

## Final made easy

In Java, the `final` modifier is required to make a local-variable non-assignable. That's a very useful property, because it eliminates a large group of potential errors. However, `final String myString` is a mouth full. Hardly anybody wants to deal with this. In Kotlin, the character count for final and non-final variables is *exactly* the same: `var myString` for mutable and `val myString` for immutable variables.

## Smart standard library

The Kotlin standard library is a treasure trove full of pleasant surprises. Did it ever bother you that there is no elegant way to use a lock in Java with `try-with-resources`? Well, in Kotlin you have `lock.withLock{ doWork() }`. It's a small thing, but it really helps. How often did you forget to free a lock in a `finally` clause? Or unlocked the read lock instead of the write lock? The Kotlin standard library is full of such little helpers.

Here's another one: `mutableListOf<T>(T... elments)`. The Kotlin compiler is smart enough to figure out the `T` if you provide the elements. So the calls to this method will have one of two shapes:

```kotlin
// empty list, need to specify the type argument explicitly
val myList = mutableListOf<String>()

// pre-filled list, no need for type argument
val yourList = mutableListOf("Hello", "World"!)
```

Can we stop here for a moment and appreciate how this code reads like prose in both cases? Also, Kotlin distinguishes between mutable and read-only collections, while still remaining compatible with Java. That's quite a feat in and on itself and makes Kotlin code a lot more robust against subtle bugs (hey, who modified this collection!?).

## Streams, streamlined

If you want to filter a `List` in Java, you need to do this:

```java
List<String> newList = oldList.stream()
    .filter( s -> s.startsWith("a") )
    .collect(Collectors.toList());
```

That's quite a lot of text if all we really wanted to do was:

```java
List<String> newList = oldList.filter(s -> s.startsWith("a"));
```

Then where's the point of using `stream()`? By surrounding your filters (and map operations and...) with `stream()` and `collect(...)`, Java streams can be *lazy*. However, you might argue that there is no point in lazyness if all you want to do is one filter. And you are correct, for a single filter operation, a stream is overkill, both syntactically as well as conceptionally. But in Java, `List#filter(...)` simply does not exist (you can use a static utility class, but come on, that's not the same thing).

Kotlin solves this problem in a very elegant way:

```kotlin
// single filter, quick and easy
val newList = oldList.filter { it.startsWith("a") }

// streams for multiple filters (same syntax!)
val newList = oldList.asSequence().filter { it.startsWith("a") }.toList()
```

So, Kotlin gives you **both** stream-based lazy chains (called *Sequences*) as well as one-shot operations. The best thing about it is that the syntax for `filter` on a list is **exactly** the same as for a `filter` on a sequence. If you work with existing code and realize that you need another filter, simply insert `asSequence()` before and `toList()` after to make it lazy, and leave the existing filter in place.


## Long live the `Iterator`

Iterators are a very basic and powerful concept. They exist in Java since version 1.0 (which says a lot). However, Iterators are treated in a rather stepmotherly fashion in Java. You can't do a `for` loop over an iterator in the same way as you can over a `Collection`. `Collection#addAll` accepts only other `Collection`s as argument, not `Iterable`s. Hell, you can't even do `iterator.stream()`. I never understood why.

Kotlin fixes all of the aforementioned problems. Dealing with iterators in Kotlin is just as pleasant as dealing with collections, without losing the benefits of iterators:

```kotlin
for(element in iterator) { ... } /* works! */

iterator.asSequence().filter { ... } /* works! */

myList.addAll(iterable) /* works! */

```


 

## Java Interop

... is really good. As in "Really REALLY good". You can mix Kotlin and Java sources (and binaries!) in the same project without issues. There are some pitfalls though; we will cover more on that later.

## Many Platforms, one Language
Kotlin translates to JVM byte code, as well as JavaScript. Native implementations are also in the works. I think it goes without saying that this is a huge deal.


# The Bad & The Ugly

For all its merits, Kotlin also have some downsides. They are not really breaking, but noteworthy nontheless.


## No `static` modifier

In Kotlin, there is no `static` keyword. This causes some annoyance in case you just want to define that one utility method. What you have to do instead is:

```kotlin
class MyClass {
   companion object {
       @JvmStatic
       fun myStaticMethod(/*...*/) { /*...*/ }
   }
}

```

This is ugly. In Kotlin's defense, there's `const` for constants (which is roughly the same as `public static final` members in Java), and you can declare top-level functions outside any class (which effectively makes them `static`). But if you *really* want a static method, the way outlined above is the only option (as far as I know).

The `companion object` was originally introduced to ease the construction of singletons. A companion object is a singleton instance of the class it resides in. I feel that this was a bad decision. Firstly because static methods are useful and should not require that much syntax, and secondly because the Singleton pattern isn't so verbose as to warrant this shorthand, and also isn't that useful to deserve such a prominent langauge feature.


## The `open` keyword

All classes in Kotlin by default are `final`. Let that sink in for a bit.

```kotlin
class MyClass {

}

class SubClass : MyClass { /* NOPE: compile error! MyClass is final! */

}
```

This is only the tip of the iceberg. Imagine frameworks such as Spring or Hibernate, which generate bytecode at runtime that extends your class. Nope, can't do that - the class is `final`. Within the bink of an eye, all of your Kotlin classes have suddenly become incompatible with pretty much any commonly used framework. There are two ways to circumvent the issue:

 - Either declare your class as `open class MyClass` or
 - Add a compiler plug-in to the Kotlin compiler.

Neither option is very pretty. The reasoning of the Kotlin language developers is that extensibility should be an opt-in thing. Only if your class is *intended* to be extended, it should be able to be extended. In my opinion, this reasoning is flawed: defensive programming best-practices will tell you that you should prepare your code to work in the most unexpected of environments. If you write a class that potentially breaks when it is subclassed, you wrote a bad class. Kotlin gives the utterly wrong incentive here, by simply sealing off everything.

## Constructor Galore

Kotlin as a langauge has a really odd fetish for constructors. Have a look:


```kotlin
// primary constructor
class PersonA(val firstName: String, val lastName: String) { }

// regular constructor
class PersonB {

    val firstName: String
    val lastName: String

    constructor(firstName: String, lastName: String){
        this.firstName = firstName
        this.lastName = lastName
    }

}
```

Both examples above **do the exactly same thing**. You may argue that the variant with the "primary constructor" is much shorter, thus better. I disagree, for the following reasons:

- The primary constructor must always list (and assign) all fields. This is super annoying when you require an additional transient field for local caching purposes.
- The class declaration line gets exceedingly long.
- If you extend from a base class which has a primary constructor, you...
    - ... **must** declare a primary constructor yourself
    - ... **must** call the primary constructor of the base class
    - ... **must** do all of this in the class declaration

In summary, once you are locked into the primary constructor workflow, you can't break free anymore. Your class declarations quickly grow to enormous sizes. Consider this:


```kotlin
class Student(val firstName: String, val lastName: String, val studentId: String) : Person(firstName,lastName) {

}

```

Of course, you get a lot of mileage out of such a line. It declares a subclass, declares the fields, declares the constructor, states the base class and finally states how inherited fields should be filled. That's a very high information density right there. I still don't like it. It's too much information packed together, and can be really difficult to parse for a human. I do not recommend the usage of primary constructors (except for data classes where you have no choice), but sometimes you are forced to use them because of the classes in the standard library.


## Data Classes - a brilliant concept with half baked execution

Data classes in and on themselves are a brilliant idea. Have a look:

```kotlin
data class Person(val firstName: String, val lastName: String)
```

 You specify the fields of a class and their types, and you get:

- the specified fields
- getter/setters
- a constructor which does exactly what you would expect
- hashCode
- equals
- toString
- clone
- various utilities

... all for free, *without writing them*. That means your `hashCode()` will never go out of sync with your `equals()`. Your `toString()` will never forget to print a field you just recently added to the class. All of this is possible because there is no text representation for these things; the compiler just generates the bytecode directly. It's a really cool concept and vastly superior to generating all of the aforementioned things via an IDE (because the generated code can get out of sync easily).

The problem is that it wasn't thought through until the end. The main issue is that you cannot use inheritance when writing data classes. Data classes cannot extend any base class, and are `final` themselves (nope, you can't use `open` here either). The reasoning behind the decision was that things might break (in particular in the clone utilities) when you try to introduce inheritance. In my opinion, this is total rubbish. UML and Ecore have shown how to correctly have data classes with multiple inheritance and cloning utilities. 

The lack of inheritance options drastically limits the applicability of data classes. Even simple Data Transfer Objects (DTOs) or JPA entities (prime examples where data classes would be ever so useful!) often require inheritance.


## Abundance of Keywords

Kotlin has a very large set of [Keywords and Operators](https://kotlinlang.org/docs/reference/keyword-reference.html). Kotlin (like C#) has the notion of "Soft Keywords", meaning that a certain word is only considered a keyword in certain contexts, but can be used as an identifier (e.g. a variable name) in all other places in the code. Some of them are quite... arcane and awfully specific, such as `crossinline`. Regardless, this makes the language quite difficult to learn, and even more difficult to master, as you have to know all of these keywords. In regular usage, you don't need more than you would need in Java. But if you want to read and understand the library functions, you will encounter them quite often. I personally think that a lot of these keywords would have been better expressed by annotations (e.g. `@Actual` instead of the modifier `actual`).


# Verdict

I absolutely recommend any Java programmer to at least have a closer look at Kotlin. In very many areas it is a far superior language than Java. It carries over all of the Java infrastructure and libraries, and adds a lot of smart design choices on top. It has a better type system for enhanced safety and compile-time error checking. The fact alone that it is `null`-safe by default should be enough to justify the use of Kotlin over Java. However, it isn't perfect. Kotlin makes it easier to write code that is short and concise, but it can also be used to produce a mess of seemingly random character combinations that nobody can decipher anymore after two days. However, in spite of its shortcomings, weaknesses and the occasional strange design choice, it is an excellent language.

Do you have any experiences to share with this language? Do you use it in production, and are you happy with it? Leave a comment below.