There are several reasons why Java developers should be looking into Kotlin. There’s the full Java interoperability, there’s the Null-safety built in, there’s the reduced boilerplate — do more with less code — as the language is really succinct, and there’s a ton of more modern features, built from engineers to engineers, that will ultimately make the developer happy by the end of the day. Language specifics, however, won’t be the primary focus of this article, as much has already been said and highlighted on that regard. Instead, I’ll talk about my personal experience in shipping Kotlin into production and describe some strategies that guided me along the way.
As the new year arrived and our resolutions already begun to fade into dev null, there’s one I’m usually able to keep: learning a new technology and push it into production.
As you might have figured by now, I’m talking about Kotlin.
From a Java developer point of view, Kotlin is a serious game-changer. While it delights every clean-code-obsessed developer by reducing most of the boilerplate code, the interoperability with Java built at it’s core makes it easy to incorporate it into both old and new apps, providing the freedom of reusing the all the beloved Java libraries and frameworks.
At Sky, we rely on Java to deliver most of our core back-end services, however we cannot really say we follow a single specific software stack. Usually different teams and projects are free to choose which languages and frameworks work best for the problems they’re solving. I must say I’m a huge fan of code ownership: “we write it, we own it and if it blows on our face by the end of the day, that’s on us”; increased responsibility; technical growth; pride and accomplishment achieved; more players and less pawns. These are all things all of us should envision and pursue. That being said, I’m also aware this approach may not work for everybody nor fit every company’s development process. Whether or not your team has full control of the technology stack, generally speaking, undertaking change such as introducing a new programming language, can be hard for several reasons:
- Harder to recycle in-house knowledge
- Loosing speed with steep learning curves
- Hiring specialists is difficult
- Breaking interoperability with the existing ecosystem
- General fear and uncertainty
While the above is true when shifting to entirely new technology stacks, for the migration from Java to Kotlin, it is mostly not.
When accessing migration risks, one should always keep in mind how easy it is to maintain the code in the long run and how easy it is to hire engineers specialised in the new technology. My perspective is that it is key to keep the engineers motivated by promoting challenging projects with healthy codebases along with the right toolset they find amusing and productive. If we’re hiring for Kotlin or any other emerging language or stack, we’re also sending a message: we’re not afraid of embracing new challenges and we’re not willing to get stuck with legacy or outdated code bases. Let’s face it, when we’re aiming to hire the best and the brightest in such a competitive market filled with so many good and innovative job opportunities as ours, we’re not selecting developers, developers are selecting us. We really need to stop for awhile and access why we are appealing and what differentiates us as companies, both from a technical and from a personal growth point of view. It’s no longer just about the pay-check. Particularly looking at the Java ecosystem, hiring for Kotlin can really give us leverage, as most of developers are eager to try new technologies, not only on their side projects but also value that kind of opportunities at their work environments.
Regarding the interoperability with the existing ecosystem, as a JVM language, Kotlin produces bytecode that runs on the very existing Java infrastructure. This is valid for microservices, container-based apps, monoliths or standalone running applications. One thing we all value in the Java ecosystem is the variety of available open-source libs and tools that make our lives easier, preventing us from solving the same problems over and over again. We all love that. By using Kotlin we can still profit from that ecosystem, thanks to its interoperability with Java, allowing to re-use the libs we’ve spent so much time and care developing, and ultimately that our employers paid for.
So, where to start?
There are several approaches one can follow to start using Kotlin on existing projects. And remember, it’s not only you who’s pushing the technology that need to gain confidence, It’s everyone involved — it’s the dev-ops team, that dynamically-typed languages lover, that bash-shell-scripting expert, the highly skilled Phyton ninja, it’s the recently-converted CSharper, and that Java4life dinosaur… — yes, it’ll take some time convincing but it’s crucial to get everyone on the same page, so better start bringing some cake to the office. If you find yourself in a position where it’s difficult to get everyone’s approval, do the right thing: don’t get a bible and start preaching every morning; maybe start by discussing the features that you’re exited about during the coffee breaks; add value, take some of your personal time and build something — you’d already be experimenting with Kotlin on your side-projects anyway — and share your enthusiasm. That will eventually get you where you want to be. Remember that by the end of the day it’s the team effort and cohesion that makes all the difference.
Start by migrating the testing modules in the least disruptive way possible
Kotlin is very expressive and follows a “readability first” approach which can be very helpfull while testing. The camelcase naming that makes tests hard to read in Java can be replaced by the use of spaces in test names, as long as they’re surrounded by backquotes.
public void testHandlesExceptionOnDateTimeParsing() // Java
fun `Handles exceptions while parsing dates`() // Kotlin
Small features like the above can make the test implementation phase way more interesting and a good place to start, as you’ll progressively discover new ways of expressing your code and gain speed.
Start by reimplementing small functionality or refactoring while addressing technical debt
Another good approach is to start by implementing new non-core features in Kotlin, taking advantage of the interoperability with Java. Several examples on how to properly do this are extensively covered on the official Kotlin documentation, as the language itself was built aiming to achieve this kind of coexistence in a painless way.
Your favourite IDE will also make things easy for you
The guys from JetBrains implemented a handy Java to Kotlin converter that really works.
These are all good ideas for a good first contact with the language features, but, let’s be honest, you can only gain real confidence and build momentum once you ship it into production and get it accurately measured. In order to avoid the middle-of-the-night phone calls and the awkward silences on those post-mortem meetings no one’s fond of, at Sky we followed the non-disruptive approach by writing from scratch new non-core features like metrics standardisation and publishing. We’ve wrapped the new functionality as a simple library which enabled us to plug it into different microservices and to identify different behaviour patterns on intrinsically different services. After extensive benchmarking and load testing, ensuring we still had our 99th percentiles under control, we started gathering good feedback and acceptance among engineers. Now we’ve reached a point we’re proudly shipping new microservices built 100% in Kotlin, exposed to hundreds of requests per second.
We were also able to stick to the frameworks we know well and already rely on, in order to avoid adding more to the learning curve. This also allowed us to compare the speed between building in full-Java vs. full-Kotlin. It’s important to notice that development-stacks like Spring, Vert.x, DropWizard, Ratpack among others, as well as static code analisys tools like SonarQube are already Kotlin ready.
Changing the core language used to deliver your precious back-end services is never a decision to be made lightly. Transitions to totally different ecosystems at backbone level usually are only feasible for green field projects that are loosely coupled to the existing ecosystem. This usually means that you’ll be tied to the present choices for the next decade, so better choose carefully. Kotlin has been gaining a lot of visibility, partially due to the growing traction form the Android, that can easily misinterpreted as hype or fashion. I personally believe it is not just hype — it’s a transition that feels only natural from both a technical and a management perspective — something the curious reader will clearly agree upon once he writes and runs its Kotlin application. On the other hand, this doesn’t mean we should, however, stop writing Java. Although the community has done a tremendous job on JVM’s backwards-compatibility, performance and reliability along the years, the Java language itself reached a point where it wasn’t evolving so well compared to other languages, mostly due to slow release cycles and, in my personal opinion, excessive conservatism. Fortunately, it looks like things are changing as Java, that language we’ve learned to master and love, still holds its sweet spot among the most used in the industry.
As a snack, here’s 2 cool Kotlin features for you:
Extension functions are really a great way of extending a class with new functionality without having to create wrapper types that inherit from it.
The above readText() snippet load a resource file, reads all its content and converts it into a String. All in one concise line, that can be used as follows:
Kotlin 1.3 introduces following types for unsigned integers:
kotlin.UByte: an unsigned 8-bit integer, ranges from 0 to 255
kotlin.UShort: an unsigned 16-bit integer, ranges from 0 to 65535
kotlin.UInt: an unsigned 32-bit integer, ranges from 0 to 2^32 - 1
kotlin.ULong: an unsigned 64-bit integer, ranges from 0 to 2^64 - 1
Unsigned types support most of the operations of their signed counterparts.
Give me feedback: clap if this is a good read ;)