The impact Kotlin had at Crunch in just one year
Over the past year, the Kotlin language has made slow but steady progress among our Java microservices, so it’s time we told the story of how this process started, how things are working out for us, and how the future looks.
Although Kotlin is seen as new, it dates back to 2010. While fashions come and go, Kotlin seems to have weathered public scrutiny and gone from strength to strength.
Why we started with Kotlin
Interoperability with Java
Java isn’t going away, and we wouldn’t want it to. We use version 12 in all our microservices, and it’s working well. Realistically, supporting a second back-end language was only going to be acceptable when we could demonstrate no significant runtime impact, no significant change in build (Maven) or in deployment (Helm chart) automation. Furthermore, we didn’t want our developers to have to think differently about their code. We’re not really looking for different software paradigms — we wanted a productivity aid with a shallow learning curve.
Making Every Line Count
Code that is verbose and full of boilerplate is tiring for developers to read and to write. It distracts from the true purpose and behaviour of the code, promotes cargo cult programming (“If I paste these lines from A to B, it will definitely start to work!”), and can result in excessive unit-testing. On the other hand, excessively terse code that conceals its purpose and behaviour simply shifts complexity from your code to the framework code beneath.
This isn’t necessarily a frivolous point. Our source code is a living document as well as providing well-tested functionality for our customers. We need it to be owned and maintained and kept fresh. If, by migrating parts to Kotlin, we can up-skill our developers, keep them enthusiastic about their craft, and motivate them to keep our code and tests fresh and alive, then that’s good for the developers and for overall quality.
Most of us use IntelliJ, also by JetBrains. We felt this would give us the tools we needed to be efficient without seriously risking lock-in.
As developers, we wanted to reinforce our investment with the new technology and also take our place in the local development community. We wanted to meet like-minded developers, learn, compare notes, and hopefully also establish Brighton and Hove as a centre of Kotlin interest. That’s why we created the Brighton Kotlin Meetup group and are planning to start hosting events later in the summer.
How we did it
While a handful of non-functional, technical utility services were migrated fully to Kotlin, in general, we decided to focus on migrating those parts of services where we expected to see the biggest benefits in code and boilerplate reduction.
Our refactoring approach:
For each slice (e.g. domain classes), starting with main code only (not tests):
- Apply IntelliJ “Convert Java File to Kotlin File” to the selected package(s)
- Manually fix any conversion errors
- Perform an IntelliJ code inspection and apply (95% of) Kotlin recommendations
- Check all tests
- Manually inspect all code: is it as clean and concise as we would hope?
- Repeat refactor for tests
- Migrate Mockito to MockK & springmockk, replace AssertJ with Strikt.
The Kotlin Way:
Being well aware of the ugly hybrid Java that I wrote when bringing my former C++ mindset to Java in 1.1 days, I’m determined that we write Kotlin as it was designed to be written and used, even if that does appear to conflict with the need for interoperability. Ultimately, if we didn’t take the time to keep our Kotlin code honest, we wouldn’t get the full benefits and we’d end up with two extra codebases, not one.
We decided to focus our migration here. As the example shows, 21 lines of code are used to convey only four pieces of information: there are id, email, and enabled properties, and they are immutable.
This is probably one of the better examples. Other examples are mutable, some have a mixture of different (and often unnecessary) equals/hashCode/toString implementations that more than double the size of the code.
We’re looking for a syntax that conveys all the same information but in the most readable form.
It’s important to have enough real-world examples of Kotlin usage to prove its usefulness and to help developers starting the journey. Beyond that, though, and leaving aside business priorities, the choice of which language to use is down to the individual development team: what they’re willing and able to support, and what makes them productive.
How it worked out
We seem to have found the right balance of boilerplate code reduction with clear language constructs and library functions that are simply more evolved than Java’s.
We’ve seen LOC (“lines of code”) reductions of:
- Around 10% for the tightest, most polished Java codebases
- Around 20% for flabbier specimens
- As much as 40% for projects that are domain/DTO/entity-heavy, i.e. projects that manage domains and largely move data around.
You’re rewriting working code?
Of course! The example above boils down to just:
In almost all cases the result is at least as clear as our existing code, with a better signal to noise ratio, and free of code that only exists to fulfil a language or framework obligation — that contributes nothing to our business functionality but that nonetheless requires unit testing.
An example git diff of a small DTO migration:
In particular, Collection streaming is clean, elegant, and clear; and Kotlin developers are spared the need to continually shuffle data in and out of collections:
Kotlin Starter POMs:
We already use Maven “starter POMs” to dramatically simplify our microservice dependencies. With extra Kotlin dependencies and new test libraries, the last thing we wanted was to add bloat when deciding whether to integrate. So we simply created a new set of starter POMs with a “kotlin-” prefix. These (usually) include the Java dependencies while adding the relevant Kotlin ones.
It’s true that Kotlin brings with it extra Maven plugin configuration, but that’s the full extent of the required boilerplate. A quick search/replace of two or three artefact names is the only other thing we need to do, and this keeps any pull requests clean and focussed.
All in all, we’re reaching a happy equilibrium between Java and Kotlin:
- Roughly 10% of our microservices are Kotlin only
- Roughly 30% are mixed Kotlin and Java. Of these, generally, about 10–15% of the overall lines of code are Kotlin, though this figure understates Kotlin’s impact within the service
- The majority of our microservices are still Java only, though this may change.
We feel that Kotlin has made us more ambitious about our back-end development, and less happy to accept bloated, duplicated, ugly code. That, in turn, spurred us to switch from Java 8 to Java 11 and then 12 within the past six months. Java’s improvement has been noticeable and appreciated, but Kotlin gives us another option. We’ve gained:
- Higher expectations about code quality
- More options to solve problems in an elegant way
- Almost inevitably, less code to maintain
- A restatement of developer freedom, and hopefully a renewal of developer enthusiasm.
Written by Andrew Regan, Technical Architect at Crunch.
If you’ve enjoyed this read please take a look at our other Crunch Developer Medium articles, and if you’re looking for a role in the Brighton area, take a look at our current vacancies.
📝 Read this story later in Journal.
👩💻 Wake up every Sunday morning to the week’s most noteworthy stories in Tech waiting in your inbox. Read the Noteworthy in Tech newsletter.