It was early 2019, we had the opportunity to start a new service on our team. At that point, most of the department was either working on Java or NodeJS, and our whole team had been working with Java Backend services for a while. After a little brainstorming session, the team expressed a desire to try something new – launch this service in a different language. We decided to take a look into Kotlin for Backend.
What is Kotlin?
Kotlin is a cross-platform, statically typed, general-purpose programming language with type inference. Kotlin is designed to interoperate fully with Java, and the JVM version of Kotlin’s standard library depends on the Java Class Library. It supports Javascript and Native Applications.
The most important thing here is that since it runs primarily on the JVM, it provides good compatibility with Java code and libraries/frameworks. So we don’t have to start with zero knowledge, and rather in a reasonably familiar environment.
Where did we start?
The first step was to run a small proof of concept with the technologies we had agreed on for the new service: Spring Boot and DynamoDB. Through this proof of concept, we were able to get a feel for what we would need in order to develop the service, such as Spring, Unit and Integration Tests, Validations, storing data in the database, and so on.
The second step was to set up a discussion with the Mobile team about Kotlin. After a long conversation with them, we got some good insights on what to expect during our first steps with the service, and they also committed to reviewing our first pull requests.
As it turned out, the code reviews worked perfectly. The first thing we did as Java Developers when we started programming in Kotlin was write Java code in Kotlin (and going back and forth implementing features in different applications resulted in a lot of Kotlin code having semicolons, and Java code having compilation errors on missing ones).
Code reviews done by people who had been using Kotlin for a while helped us to open our minds to features and patterns we would never have previously tried.
Why Kotlin?
Reduced scope and clear domain
Due to the fact that we started a new project with a reduced scope and clear domain, we were able to be more free in our choice of technology, with less impact, so we could better assess whether it would be suitable for other projects in the future.
Large community and growing
Since 2017, Google has made Kotlin the default language for Android programming, which after almost 4 years means there is already good community support. For this reason, we felt that it was relatively safe to start – in terms of research material, and since it uses the JVM, in terms of performance/deployment knowledge.
Adopting Kotlin is easy
- You don’t lose basic knowledge of Frameworks or JVM.
- In general Kotlin is great, the interop works very well, and the syntax is much less verbose
- We had a team of Android developers using Kotlin who could help with any syntax challenges.
Advantages of Kotlin over Java
Less code
Most of us dealing with Java know the frustration of having to write a lot of boilerplate code to get to the actual part where the code is needed. With Kotlin, a lot of that is reduced because Kotlin was designed to solve, or at least reduce, the community’s complaints about Java. This is a very important aspect of Kotlin, it allows you to write less code, and focus more on what’s important, while worrying less about language details.
Null handling
Kotlin has support for non-nullable variables built in, which means that when you declare a variable, you have to explicitly state whether the value can be null or not, and the compiler helps you check if you are using something that is nullable and forces you to handle it. In theory, the “The billion dollar mistake”, the good old NullPointerException, is not possible. (In practice you can fall into it in some cases if you explicitly tell the compiler that something is null-safe, when in runtime it might be null – but that’s on you).
The bottom line is you always need to know if your value is nullable or not, and handle it. You don’t need to keep reminding yourself, the compiler reminds you – no ambiguity involved. This leads to fewer bugs.
Immutability – Data classes
Another driver for less ambiguity and fewer bugs is immutability. Immutability in Kotlin is a first class citizen. The language provides better support for immutability. Here’s how:
- Lists are immutable by default
- Preferred use of the val keyword to define variables (works similarly to final in Java, preventing unexpected changes to things that shouldn’t change).
- Data classes, that is, classes intended to hold data, which are preferably immutable and have enhanced capabilities for cloning the objects to prevent unexpected changes from being made.
- The Object keyword can be used to define singleton classes, anonymous inner classes, or even static members, depending on the syntax.
Actual support for functions/better built-in support for a more functional programming style
Not the default behavior in Java, in Kotlin you can create standalone functions, detached from classes. As well as that, Kotlin provides a lot of utility functions that can be applied to any object, the scope functions.
Scope functions
Scope functions help you write less code/lines with some repetitive tasks such as null handling, calling setter methods in Java classes, performing multiple operations in the scope of a single variable, and so on. As a bonus point, it helps you get closer to a Functional Programming approach, as you can chain scope functions and other methods.
Here are some of the most notable of these:
let: Very useful for null handling, creates a scope that passes the object as “it” inside, so operations can be performed on it. (Alternatively, the parameter can be named by using a lambda function).
also: useful for fluently writing statements that are supposed to happen on success of a function. For example, for logging statements after the execution of a service.
In this example, we see the usage of both “also” and let. also allows an additional function call after the execution of the method encode(). The let creates a scope of null safety, in our case the platforms variable is nullable, and we want to perform operations if it exists, so we can rely within the scope of the let function that it is not null and will do what we want (namely called the incrementFormActivity method).
use: Works with the Closeable interface, provides the close functionality once the scope is closed. Useful for maintaining a cleaner syntax and not forgetting to close resources after use.
In this example, we can see how we can use the Closeable Interface by using use. The BufferedReader will be closed after leaving the scope of the function.
with: Passes the object inside of the scope as “this”, allowing for simplified usage of getters and setters, for example.
In this example, we can see how it can be useful to simplify the code around many operations to be executed on a single object. We can instead use this (and omit it, if it is clear enough).
apply: Passes the object inside of the scope as “this”, while returning the reference of the original object.
In this example, we can see how it can be useful to create an object, fill some properties, and still return it in a single statement.
Unit Tests with MockK
MockK is a library written in Kotlin to write amazing Unit Tests. This library provides us with the ability to write mocks easily, with heavy use of Kotlin DSL and extension functions that allow you to write pure and beautiful Kotlin code, while writing little. It also comes with good Spring Boot integration, possibility of creating Beans, including official Spring documentation on MockK tests when using Kotlin.
In this example, we see the usage of mocking methods by using the “every” directive, and then verifying its usage with the verifyAll command. As well as that, we see the mocking injection being handled by the MockkExtension and Spring.
IDE support
As Kotlin is written by JetBrains, which is the publisher of the IDE that the team was using, the IDE provided great support to us while writing code in Kotlin. All the usual tools that we were already used to, code assistance, it even has its own built-in converter (from Java to Kotlin).
We wouldn’t recommend the usage of the converter long term – but it certainly helped in the bootstrapping of our first project, and in understanding and discovering some cool features that we weren’t familiar with at that point.
Coroutines
Kotlin natively brings support for coroutines, something that is not yet easily accessible in the Java api. Coroutines are comparable to a thread, but more lightweight in that they have no context switch cost when moving from one thread to another in a blocking scenario. Granted, concurrency is something we don’t use often in RESTful services, but it’s a nice bonus when we need to deal with it.
In this example, we can see how we can launch coroutines, to process multiple external calls in parallel. For each case, we close it and its assets in a different coroutine execution, guaranteeing better resource usage and shorter response times.
And much more..
Other advantages of Kotlin over Java include: extension functions, named parameters, default values, and lots of quality of life/syntax sugar features.
Our experience
What we used:
We chose Kotlin with Spring Boot 2 (MVC, WebFlux, Gateway and Security), using integrations with AWS SDK (mainly DynamoDB and SQS, but also ElasticSearch, Redis, DocumentDB, Postgres and JWT library (java-jwt) for handling JWT tokens.
Kotlin’s integration with Spring Boot was very good, we hardly had any problems with it. With other libraries we had occasional problems with nullability and annotations – more on this in the challenges section below.
Learning Curve:
As mentioned earlier, we started out with what looked like Java written in Kotlin, and the further we progressed, the more we grasped the spirit of Kotlin. It was a learning experience that took a while – as we discovered the new features and applied the code reviews, our speed increased. We had a lot of fun throughout the process. Once we had a larger codebase with more examples, new members were able to start contributing very quickly and even help expand the codebase and overall knowledge of the team. We had more than 5 new members who had no prior experience with Kotlin, who almost immediately started contributing and learning from the code reviews of the other team members who had more experience.
Overall impressions:
We find that it’s more fun to work with Kotlin, there is less code to write, and it’s an easy transition – even if we didn’t have the purest kotlin code at the beginning, we had no problems writing it, as the interoperability with Java helps a lot with the overall knowledge. The team was more than happy to learn new things. It’s definitely harder now to go back to writing code in Java. When we do, we miss a lot of quality of life features and changes we got used to in Kotlin.
Challenges
Lack of real backend examples using Kotlin code
At the time, most of the documentation was still focused on mobile. There weren’t as many examples of backend development to integrate with libraries. Whenever we tried something new, for example Spring Boot and Dynamodb, we often didn’t find more than the very basic examples using Kotlin. We had to search for Java examples, and only then were we able to write Kotlin code. Fortunately, we’ve come a long way since then. Many companies and teams are adopting Kotlin for backend, and it’s easier to find articles or videos about Kotlin.
Annotation Use-Site targets
One of the most interesting features of Kotlin is the data class. However, one small frustration we faced in the beginning was that we couldn’t use data classes as often as we would have liked. First, we couldn’t use data classes for @Entity objects due to the nature of Spring Data/Hibernate, which uses getters and setters to create the objects. Following that, we faced the same problem with our DTO layer. The regular javax @NotBlank validation didn’t work with data classes. To use it, we had to write our DTOs as regular classes. Finally, we found a feature called Annotation Use-Site target. This feature creates a bridge between the Kotlin code we write, and the Java code generated underneath that the library will use, allowing functionality as intended.
Libraries in Java can occasionally have problems with Nullability interoperability
This one is a bit annoying because if the Java code inside libraries does not well define whether something is nullable or not, in Kotlin you won’t be able to use the non-null variables, and sometimes it won’t even recognise and use an intermediate representation (platform type). The sad part is that these platform types are not immediately null safe, and need to be handled carefully.
There are some cases where the annotation of @Nullable and @NotNull (among others) are well interpreted, so if you have control over the library’s code, you can get better results by adding the right annotations. However, if you don’t have control over the library, then you need to handle these types carefully on the Kotlin side as well, or alternatively, you could add an anti-corruption layer.
What’s next?
Today, we have 13+ services running in Kotlin in our department, and even more across Delivery Hero. The services have proven to be very stable, with very few issues along the way, and many benefits. More and more teams are adopting Kotlin for backend at Delivery Hero, and we would definitely recommend it.
We will probably start looking into other frameworks and libraries designed for Kotlin, for a more pure integration, like Arrow and Ktor, among others.
Thank you, Fernando & Richard, for sharing your learnings with us!
If you love Kotlin and would like to check out our Kotlin opportunities, check out some of our latest openings, or join our Talent Community to stay up to date with what’s going on at Delivery Hero and receive customized job alerts!