At Talkdesk, we have this software component that provides a small but important data bridging functionality.

The main use case of this component is:

Accept HTTP POSTs on a specific endpoint Parse the request body, splitting it into several entries Process each entry:

Check the entry type

Drop the entry if it is to be ignored

Parse the entry using a Regex and build an object from the content

Publish the object as a JSON message to a message broker

Although it looks like a fairly simple tool, there are some relevant non-functional requirements to be considered:

Load: It must be able to handle an average load of 800 requests per second (RPS), sometimes peaking at 1400 RPS

It must be able to handle an average load of 800 requests per second (RPS), sometimes peaking at 1400 RPS Payload: The usual payload size ranges from 100 Bytes to 20 KBytes

The usual payload size ranges from 100 Bytes to 20 KBytes Resilience: It must resist to instability on the connection to the message broker

This component was originally written in Java 8 and use DropWizard as the foundation framework for the Web API. The code base had 1566 lines of code (LoC).

Kotlin

Example of Kotlin code

At Talkdesk, we use Ruby for most of our code base because we love its simplicity and expressiveness. But we do have a few applications running on the JVM that have been written in Java, Clojure and Scala.

After a few months writing this specific component in Java, we felt the shortcomings of the Java language when compared to modern languages (like Ruby, Rust or Kotlin). As such, we really wanted to find an alternate imperative and compiled language that could run on the JVM.

Considering that there are people at Talkdesk who have experience with Kotlin, we decided to give this language a try, because it would be a low-risk component to test such a migration.

Some (not all) of the factors that triggered the migration were:

Reduce walls of text: Java is very verbose compared to other modern languages, which ends up a dense code base that becomes difficult to read, maintain and review

Java is very verbose compared to other modern languages, which ends up a dense code base that becomes difficult to read, maintain and review Null safety: Kotlin is a null safe programming language, which means that the developer must be explicit about whether a variable allows a null value. Null safety really makes the code explicit about how null must be handled on the application code

Kotlin is a safe programming language, which means that the developer must be explicit about whether a variable allows a value. Null safety really makes the code explicit about how must be handled on the application code Take advantage of language goodies: Kotlin has several built-in goodies that allow developers to do complex stuff with few lines of code. For the use case of this application, this is especially useful when it comes to manipulating collections

Kotlin has several built-in goodies that allow developers to do complex stuff with few lines of code. For the use case of this application, this is especially useful when it comes to manipulating collections Decrease the pain on Code Review: Once we get used to reviewing Pull Requests (PRs) on a modern language, we start to feel the real pain of reviewing PRs with the verbosity of Java

Although we felt the need to modernize the code base, there were some items that we weren’t willing to let go:

Use the JVM: JVM is a very stable and mature runtime environment and is fast enough for the use case of this application

JVM is a very stable and mature runtime environment and is fast enough for the use case of this application Debuggers and profilers: The Java ecosystem provides several reliable tools for advanced debugging, profiling and troubleshooting

The Java ecosystem provides several reliable tools for advanced debugging, profiling and troubleshooting Make use of Java libraries: Third party software suppliers often provide enterprise-grade drivers and libraries for most widely used languages (such as Java). These libraries tend to be very stable and reliable as manufacturers invest significant amounts of effort on them.

Third party software suppliers often provide enterprise-grade drivers and libraries for most widely used languages (such as Java). These libraries tend to be very stable and reliable as manufacturers invest significant amounts of effort on them. Run Java and Kotlin side-by-side: If we come to migrate other Java applications to Kotlin, it would be a plus to be able to do it iteratively as opposed to a big-bang migration.

So, we decided to migrate the code to Kotlin!

The migration

The tool that provides the best experience to write Kotlin code is IntelliJ IDEA (Kotlin was actually created by JetBrains, so… you get the point).

To support the code migration, we used an awesome feature of the Kotlin plugin for IntelliJ IDEA that provides automatic conversion of Java code to Kotlin code.

So, let’s get down to business:

First, we needed to update the pom.xml to compile Kotlin. (There’s also instructions for updating gradle)

After adding Kotlin support to pom.xml , we are able to compile both Java and Kotlin which gives us the benefit of having both code bases living side-by-side.

Then we started converting class by class by applying this simple process:

Open the .java file Copy all the code to the clipboard Create the corresponding Kotlin .kt file Paste the code Politely say Yes when IDEA asks if we wish to convert Java to Kotlin Go over the class and improve the code whenever we felt the need

That’s it!

It’s done!

Human tweaks on converted code

The converter is a bit conservative on the code conversion, so we needed to perform a few human-based tweaks.

The most relevant tweaks were:

Adjusting nulls

Unless a variable is annotated with Java’s @NotNull , the converter will make the assumption that the variable can be null . As such, we need to manually check the variable’s possible values and make the changes related to null safety.

The converter will keep every @NotNull annotation from the Java code. Unless, these annotations are being used for documentation, they can be removed.

Inline variable initialization

When an instance variable is initialized on a Java constructor, the converter will generate a Kotlin constructor and initialize the variable on it.

Most of the time, the variable initialization can be inlined along with the declaration, making the code more simple to read.

// Original Java code

public class Foo {

private final String bar; public Foo() {

bar = "baz";

}

} // Auto converted Kotlin code

class Foo() {

val bar: String constructor {

bar = "baz"

}

} // After review:

class Foo() {

val bar = "baz"

}

Replace “companion object” fields with compile time “const”

On a Kotlin class, all variables and methods are instance-scoped, so it does not allow declaring a variable or methods as static . Static members must be declared on a companion object.

The converter always generates Java static members as members of the companion object .

But sometimes, these can be replaced by a compile time constant const (for variables).

// Generated code

class Foo() {

companion object {

val bar = "baz"

}



// Foo methods and properties

} // After review

private const val bar = "baz"

class Foo() {

// Foo methods and properties

}

Keep or remove `@Throws` annotation

Because Java requires methods to explicitly indicate which exceptions are potentially thrown on the code using the throws keyword on the message signature, the converter will generate a @Throws annotation that is used for documentation.

// Generated code

@Throws(BarException::class)

fun foo() {

// code that might throw a BarException

}

Since Kotlin does not require explicit mention of the exceptions that can be thrown from a method, these can be removed from the code if not being used to generate documentation.

Replace one-line methods with Kotlin Single Expression method

Methods on Java code that only have one line of code are converted to Kotlin methods with the converted code.

Kotlin supports Single Expression Functions which enable implementing one-line methods on a single line of code.

// Java

public int square(int x) {

return x * x;

} // Auto converted Kotlin code

fun square(x: Int): Int {

return x * x

} // Reviewed using Single Expression Function

fun square(x: Int) = x * x // Unnecessary to declare return type

How did it end?

We were able to complete the migration in about six man.hours of effort: three man.hours for converting the code and three more man.hours for peer code review and tweaking.

The code base went down from 1.566 LoC to 1.128 LoC. That’s 23% less code base to maintain and work on! Not bad for a six hour investment :-)