Kotlin — Efficient code finds your next flight

It was September of 2017, and Javazone marked the start of autumn yet again. Eager Java developers filled Oslo Spektrum looking for good food, merchandise and fun activities. And of course there’s the abundance of presentations.

We had been looking at Java alternatives for a while, but none really fit the mark. Groovy is nice for testing, but it has sloppy static typing that leads to unreadable code. And Scala just seems like Perl for Java coders, what’s up with all the p’s? Anyway, Kotlin had seemed promising for a couple of years, and we found Jesper Holmberg’s presentation to be very convincing.

After most of the presentations were done, two developers from FINN Travel put their heads together and added some Kotlin mojo to their code base. It was easy to convert some classes and have them live alongside our Groovy and Java classes, making the transition seamless. By the time they headed back to the after party (and the free beers), Travel Flight Search was already running Kotlin code in production!

After this first promising peek into the wonderful world of Kotlin, four hackers from FINN Travel went to the first ever KotlinConf 2017 in San Francisco a couple of months later. Fun times were had, and Kotlin has since gradually made its way into FINN’s entire codebase.

A better language

Kotlin is not a revolution in the programming language world. However, it comes with many improvements as compared to Java. For example, it avoids complex language features, such as unrestricted operator overloading and macros, which tend to do more harm than good. In this section we’ll go through what we think are Kotlin’s biggest advantages over Java: null safety, less code, being immutable first, extension functions and function types.

Null safety

The null reference in programming languages is known as the billion dollar mistake. The null reference in itself is not a problem: the problem is how it’s implemented. In Java, and many other languages, you have no way of knowing whether something is null, or not. The only sane way of dealing with null references is for the language to make it explicit and force you to deal with it, which is how Kotlin approaches null references.

In Kotlin, null safety is built into the type system. The type system distinguishes between nullable types, which can be null or hold a value, and non-nullable types that cannot be null. The following example illustrates the basic usage of nullables.

val name: String? = "Guybrush" // a nullable String
val length = name.length // compile error
if (name != null) {
val length: Int = name.length // compiler knows name is not null
}
val length: Int? = name?.length // safe call returning nullable

In this example, accessing name without checking for null gives us a compilation error. Alternatively, if we do a traditional null check first with an if statement, then the compiler allows us to access name directly. The last line in the example shows the safe call operator. When calling name?.length we get a new nullable which holds a value if name held a value, otherwise it will also be null.

Nullables make for more understandable code that eliminates defects. You no longer have to reason about whether a value can be null or not; it’s always explicit with nullables. Nullables in Kotlin have been criticised for being too rigid, but we find them to enforce just enough structure.

Less code

Having a language that expresses the same functionality in less codelines is an advantage as long as readability isn’t sacrificed. Of course, code readability is highly subjective. That being said, we find that Kotlin does not sacrifice readability compared to Java. Jetbrains claims that Kotlin gives a 40 percent reduction compared to Java. From our experience of converting Java applications to Kotlin, the code reduction is typically around 30 percent.

The Kotlin feature that reduces the most code when compared with Java is data classes. The following example of a data class in Kotlin is similar to a java class with methods declared for getters and setters, equals, hashCode, copy, and toString.

data class Person(val name: String, val age: Int)
val person = Person("Leela", age = 25) // specify ‘age’ with named parameter

As the example shows, Kotlin supports named parameters. This further reduces code by eliminating the need for builders. To be fair, in Java you can use libraries such as Lombok (builders) and Vavr (collections) to reduce code. Still, it’s convenient to have the language natively provide fundamental building blocks such as builders and collections.

Other Kotlin that help developers reduces code include the collection API, functions and lambdas, nullables, when expressions, and smart casts. Certain parts of Kotlin’s collection API are significantly less verbose than Java’s stream API. The following example illustrates how to group a list of persons by age, where we also associate the name of the person with the age.

// Kotlin groupBy
persons.groupBy(
keySelector = { it.age },
valueTransform = { it.name }
)
// Java groupBy
persons.stream()
.collect(
Collectors.groupingBy(
Person::getAge,
Collectors.mapping(
Person::getName,
Collectors.toList()))
);

Immutable first

Kotlin’s preference for immutability might be its biggest difference from Java. The following example compares how to work with Kotlin’s data classes in both immutable and mutable ways.

// Immutable — preferred way
data class Person(val name: String, val age: Int) // immutable
// fields
val person = Person("Starbuck", 22)
val newPerson = person.copy(age = 30) // .copy() returns a new
// instance
// Mutable
data class Person(var name: String, var age: Int) // mutable fields
val person = Person("Starbuck", 22)
person.age = 30 // update same instance

In general, the use of val (value) is preferred over the use of var(variable). This is especially true when using data classes. Val’s are the equivalent of Java’s final keyword and their object references cannot change. The example above shows how to work with an immutable data class. Instead of modifying an existing instance of Person we invoke copy() and specify the fields to be changed. copy() always returns a new instance with the specified changes applied. Collections in Kotlin are immutable by default, but mutable alternatives are always available.

Immutability might at first seem like a hurdle for seasoned Java developers, but tends to lead to more understandable and safer code over time. Once an immutable class has been instantiated you don’t need to worry about it changing, and there’s therefore not need to apply defensive techniques such as copying objects. Combine immutability with nullables and there is even less need for defensive programming.

Extension functions

Extension functions are especially helpful when working on JVM internal or third party classes that are final or otherwise inaccessible. Even the notoriously final String can be given extra methods:

fun String.fixStart(): String = this.replace("C", "Kha")
fun String.replaceEnd(): String = this.replace(Regex(" .*$"), "an!")
"Can I?".fixStart().replaceEnd() // “Khaaan!”

Function types

While passing functions as types in Java is cumbersome, in Kotlin they are first class citizens. There’s no need to remember the specific usages of Java’s Supplier, Function and BiFunction anymore. Typically (String) -> Int is a function that takes a String and returns an Int. You can find more examples in Marcin Moskala’s Kotlin Academy blog post.

Easy transition & great tooling

We have found onboarding coders into Kotlin to be a pretty straight forward experience. There are usually only some minor initial objections, such as the convention of having the type after the variable name, but that’s just about it. However, in more conservative teams we have seen larger discussions occur, especially regarding Kotlin being immutable first. One lesson we’ve learned here is to accept mutable and more Java-like code to begin with, and try to delay having such discussions until later on.

Kotlin is tightly integrated in IntelliJ IDEA, and is supported as well as Java is. This is not surprising; after all, this language was initially created to improve IDEA development. Its code converter is decent, although the resulting code needs to be groomed before it is usable.

Kotlin’s interoperability with Java is quite seamless, but there are a couple of hoops to jump through when it comes to the finer details. Annotate with @JvmStaticto make a function statically accessible, @JvmOverloads to support default method parameter arguments and @JvmField to access class fields directly. Check out the the Kotlin java interop reference guide for more details.

Many large libraries already treat Kotlin as a first class citizen. Spring 5 shipped with full support, and Google moved to Kotlin as the primary Android development language. All this helps us to trust that Kotlin isn’t just a mayfly.

Next destination

A rough, unscientific count reveals that most of the FINN codebase is Java, while around 8% of its class files are Kotlin. 3% is written in Scala and our eager Haskell developers get 0,2% of the share. Groovy is almost on par with Kotlin, mostly because of excellent test support with Spock. There’s obviously also a lot of Javascript, Python, Bash, Go and others in the mix as well.

Kotlin use has been growing fast here at FINN, and it’s here to stay. At least for now, who knows what’s in store for the JDK and the JVM in the years to come?

This article was written by Arild Nilsen and Henrik Brautaset Aronsen who both work at FINN.no