A Byte of Kotlin in a Land Full of Java
Intro
I came to the world of programming through a Java path. And during my entire career I was programming mostly in Java. When Kotlin became more and more popular in the programming world I wanted to answer the question is it worth to use Kotlin in Java project or not? And it turned out, as always in an engineering, that an answer is - it depends. This post is the way of a company I was working for and our experience. So let’s have a journey from an island of Java to an island of Kotlin together.
How was my first time?
My first acquaintance with Kotlin was when it has such a weird logo.
Back then I wrote nothing more than “Hello, World!” project in Kotlin and only followed Kotlin community to be informed about new features and changes. But ~7 years ago I tried a first time Kotlin in an enterprise project for test automation API. The project was small and went for a while.
Only in 2017 after some iterations of a rejection and an acceptance among other engineers we introduced Kotlin full time in our Java components in a production running project. This was not a huge project: 500K LOC, but was quite a while in production: for 12 years.
Main kotlin features
Kotlin is elegant, pragmatic and tool friendly language, but elegance is great, as long as it’s pragmatic. It is not a research project, but the language which was build for engineers by engineers.
![]() |
![]() |
![]() |
![]() |
|---|---|---|---|
| CONCISE | SAFE | INTEROPERABLE | TOOL-FRIENDLY |
| Drastically reduce the amount of boilerplate code | Avoid entire classes of errors such as null pointer exceptions | Leverage existing libraries for the JVM, Android and the Browser | Choose any Java IDE or build from the command line |
Kotlin ecosystem
One of the magic of Java - is its backwards compatibility, but it’s also a big bottleneck, which limits some features and makes some features implemented in the inefficient way. Kotlin was designed with keeping in mind all drawbacks of Java, but being 100% interoperable with it.
What ∆ do we expect?
Why did we decide to introduce kotlin?
In our project we had:
- A lot of Java code which was written starting from Java 1.4
- Lot of entities which are changing
- Fit enough with boilerplate code and kludges
We would like to get:
- Better code reading
- Faster feature creation
- Less routing coding
- More expressive
What does Kotlin give us:
- Code is shorter
- Null-safety
- Automatic type inference
- Great interoperability with Java
- Extension methods
- Ideal delegation
This is really all that pluses which Kotlin provides. This is what we were trying to achieve during the migration and this is what basically we got.
Disadvantages?
Everything looks so sweet by far. But is there a dark side?
Actually yes and I should say about it. If you like ternary operator, you should forget about it. And there some things you should be ready for: you will be not able to assign int to long variable, nothing will be cast automatically. You would lose some Intellij features. And you would have some bugs. Together with less code and better readability you will also get some noise. But still we have more advantages.
Code style
Important preparation step to the migration is Code Style. A new code style adoption might be a very natural process, if it starts with a new project, when there’s no code formatted in the old way. Changing formatting in an existing project is a far more demanding task, and should probably be started with discussing all the caveats with the team.

Coding conventions
There is for quite a long time like from 2018 so-called Kotlin coding conventions, but if you started before it or before Kotlin version 1.3, then you do not have all it in your project. But fortunately you have a special flag you can enable in maven or gradle which helps to synchronise your team.

<properties>
<kotlin.code.style>official</kotlin.code.style>
</properties>kotlin.code.style=officialCheck style
Do you also fail builds if a check fails?
Mixing Kotlin and JAVA
As Kotlin compiles to a Java byte code you can have both Java and Kotlin code in one project, and you can gradually add Kotlin to your existing project. Although blending Java and Kotlin works great, you can’t write neither idiomatic Java nor Kotlin code, when blending. So the goal should be to move completely to Kotlin.
Strategies for adoption
We can live with both languages in production more or less good, but this leads us to another question - how to adopt Kotlin
in practice? On the right picture you can see module with 2 source folders Java and Kotlin respectively - usually
kotlinc invoked first to compile Kotlin files, then javac to compile Java on demand if there are dependencies from
Kotlin to JAVA.
![]() |
![]() |
|---|
- Use Module Dependency Graph as a map
- Build tools: first invoke
kotlinc, thenjavac - Kotlin compiler knows when to invoke
javac
Conversion strategies
The next question is how to convert your code systematically:
- One more systematic way is to go random and wild, and do it along a task at hands. From the technical point of view this is the worst approach, but it is the best following a business value.
Here you can see two incremental systematic approaches:
- Inside-out, you take your dependency graph and first convert your innermost modules going outside.

- And outside-in you start from outside your graph and then go to the
innermost:

JAVA to Kotlin converter
Magic keys combination CMD + Option + Shift + K.
Works only in one direction. Your Java class will become Kotlin class, but most of the case you require some revision and rework. What is the difference of new Kotlin converting. JetBrains advertise improved nullability conversion.

⚠️ No tests, no conversion!
Converting: from JAVA
You can automatically convert JAVA code to Kotlin code. But does the automatic JAVA to Kotlin converter always produces an idiomatic Kotlin code? Actually not. Kotlin converter does not produce the best results:
public class Participant {
private AudioState audioState;
public AudioState getAudioState() {
return audioState;
}
}public class Conference {
private List<Participant> participants;
private ConferenceState state;
public List<Participant> getParticipants() {
return participants;
}
public ConferenceState getState() {
return state;
}
}public class Example {
public void test() {
Conference conference = new Conference();
String conferenceState = conference.getState().name();
if (conferenceState.equals(ConferenceState.ENDED.name())) {
conference.getParticipants().forEach(p -> {
if (!p.getAudioState().name().equals(AudioState.CONNECTED.name())) {
p.disconnect();
}
});
}
}
}class Example {
fun test() {
val conference = Conference()
val conferenceState = conference.state.name
if (conferenceState == ConferenceState.ENDED.name) {
conference.participants.forEach(Consumer { p: Participant ->
if (p.audioState.name != AudioState.CONNECTED.name) {
p.disconnect()
}
})
}
}
}Converting: to Kotlin
But if you’ve written your Java code so that it can be easily interpreted, the converted Kotlin code becomes better.
Refactor your Java code -> convert to Kotlin using automatic converter, then simplify. We know the strategy, we have the tool, but where to start? First we proofed that our project works with Kotlin and our team understands how to mix it. We started to refactor some old Java code to Kotlin, mainly tests, which typically means calling from Java code into Kotlin. Then we started converting small classes such interfaces, enums and small data classes written in Java into Kotlin. After that all the new features we wrote with Kotlin. You do not have to alter all surrounding Java code due to Java and Kotlin interop smoothly. Java does not know about any Kotlin running at all.
|
|
|
|
|
|
|
|
Testing in Kotlin
Naming
Some Java frameworks like Spring are known with its class and method names. Something like
AbstractPostProcessorFactoryBean and this is not the longest name, but Kotlin could do better.
You can write more readable names with spaces. However, dotes and carriage returns are not allowed.
@Test
fun `The very detailed and readable test name`() {
assertThat("String", instanceOf(String::class.java))
}Kotlin + JUnit
Probably the most popular Java framework for unit testing is JUnit. And it works out of the box with some assumptions.
You should know that @BeforeAll and @AfterAll should be defined in the companion object.
Companion is such a small singleton inside our class.
And everything what is inside this singleton is static.
There are no static fields in Kotlin.
In Parameterized tests ClassRule, MethodRule and Parameter should be defined as @JvmFields.
There should be no getters and setters generated for them, only Java field. We can use @JvmStatic so that our
@BeforeAll and @AfterAll will be generated not inside companion, but inside test class.
@BeforeAll and @AfterAll go to Companion Object
@Rule and @Parameter should be appended with @JvmField
companion object {
@JvmStatic @BeforeClass
fun setUp() {}
@ClassRule @JvmField
var resource: ExternalResource = object : ExternalResource() {
override fun before() {
conference.connect()
}
override fun after() {
conference.disconnect()
}
}
}
@Rule @JvmField
var rule = TemporaryConference()Kotlin + Mockito
Second part of the testing is Mocks. This is very hard to test huge and complicated code with a lot of dependencies, so we use mocks to avoid it. And here using classic mockito we got some problems.
anyObject() matcher returns null.
This is a problem as Kotlin checks for null-safety. When Kotlin accesses the object it checks that object is not null.
And if it is null, then Kotlin returns Kotlin NPE.
anyString() also returns null, this is strange as they could return just empty string.
Probably, it is done to save memory.
When is keyword in Kotlin and used in Mockito do define module behaviour. With when this is very easy
to solve the problem by importing it with the other name.
For anyObject: if we do not want to use third party libraries we have to write a kludge.
We can create extension function which will pass the concrete class to the mockito any().
We use function of the reified generics, it compiles in your kotlin code and type will be known there.
import org.mockito.Mockito.`when` as on
inline fun <reified T> kotlinAny(): T = kotlinAny(T::class.java)
inline fun <reified T> kotlinAny(t: Class<T>): T = Mockito.any<T>(t)The Chad Mockito
But Mockito could be beautiful and idiomatic. MockK library allows us to solve all the problems in a one shot.
val car = mockk<Car>()
every { car.drive(Direction.NORTH) } returns Outcome.OK
car.drive(Direction.NORTH) // returns OK
verify { car.drive(Direction.NORTH) }
confirmVerified(car)Problem of the no-args constructors in Java
If you are using the Java Persistence API (JPA) you will have problems there.
You would probably want to use Data classes for your entities in Kotlin.
And this is logical desire, they have a lot of perks, like equals and hashcode out of the box, Kotlin copy() functionality.
Data classes have no no-arg constructor, but you need it in JPA. This is must have requirement in JPA. As solution - do not use data classes, but then you loose all perks.
Second: use the no-arg compiler plugin. It generates an additional zero-argument constructor for classes with a specific annotation. The generated constructor is synthetic, so it can’t be directly called from Java or Kotlin, but it can be called using reflection. This allows JPA to instantiate a class, although it doesn’t have the zero-parameter constructor from Kotlin or Java point of view.
<configuration>
<compilerPlugins>
<!-- Or "jpa" for JPA support -->
<plugin>no-arg</plugin>
</compilerPlugins>
<pluginOptions>
<option>no-arg:annotation=com.my.Annotation</option>
<!-- Call instance initializers in the synthetic constructor -->
<!-- <option>no-arg:invokeInitializers=true</option> -->
</pluginOptions>
</configuration>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-noarg</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>buildscript {
dependencies {
classpath "org.jetbrains.kotlin:kotlin-noarg:$kotlin_version"
}
}
apply plugin: "kotlin-noarg"
noArg {
annotation("com.my.Annotation")
}Problem of the final classes in Kotlin
Kotlin has classes and their members final by default, which makes it inconvenient to use frameworks and libraries such as Spring AOP that require classes to be open. The all-open compiler plugin adapts Kotlin to the requirements of those frameworks and makes classes annotated with a specific annotation and their members open without the explicit open keyword.
<configuration>
<compilerPlugins>
<!-- Or "spring" for the Spring support -->
<plugin>all-open</plugin>
</compilerPlugins>
<pluginOptions>
<!-- Each annotation is placed on its own line -->
<option>all-open:annotation=com.my.Annotation</option>
<option>all-open:annotation=com.their.AnotherAnnotation</option>
</pluginOptions>
</configuration>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-allopen</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>buildscript {
dependencies {
classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlin_version"
}
}
apply plugin: "kotlin-allopen"
allOpen {
annotation("com.my.Annotation")
// annotations("com.another.Annotation", "com.third.Annotation")
}Some Language Features
public interface ParticipantDetails {
String getName();
String getLastName();
}There are no getters and setters in Kotlin. You can not write something like override get() =....
You should create private properties which obviously do not have generated getters and setters.
data class Participant(private val name: String,
private val lastName: String) : Conference.ParticipantDetails {
override fun getName(): String {
return name;
}
override fun getLastName(): String {
return lastName;
}
}Working with Annotations
data class Moderator(
@NotNull
val name: String,
@Active
val conference: Conference
) {}This is not clear that annotations applied to arguments of the constructor. Hibernate-validator does not know anything about arguments of the constructor. - keyword “field” helps us.
Notnull annotation will never be reached, as if you pass null first, Kotlin fill throw Kotlin NPE.
You probably should define field as nullable. Hibernate does not know about annotated arguments of a constructor,
but jackson knows, you can use jackson-module-kotlin, and you will have all Kotlin features out of the box.
data class Moderator(
@field:NotNull
val name: String,
@field:Active
val conference: Conference
) {}Java + Lombok = Kotlin
We use lombok in our project.
What are the equivalents in Kotlin? What we are not loosing? Is there something that Kotlin does not provide us without of the box?
| @Getters | Properties |
| @Setters | |
| @NonNull | Nullable Types |
| val/var | val/var |
Java + Lombok. Delomboking
We still can generate some of them like equals and hash code using Intellij. And we should do additionally some steps before converting from Java to Kotlin. There is tool provided by Intellij, called delombock.
| @Equals | @HashCode |
| @ToString | Synchronized |
Caveats for Java Developers
class ConferenceCall {
val id: Int = Random.nextInt(0, 100)
}fun getConference() {
val conferenceCall = ConferenceCall()
for (i in 1..10) {
println("Conference Call ID: ${conferenceCall.id}")
}
}
class ConferenceCall {
val id: Int
get() {
return Random.nextInt(0, 100)
}
}fun getConference() {
val conferenceCall = ConferenceCall()
for (i in 1..10) {
println("Conference Call ID: ${conferenceCall.id}")
}
}
Learn tools, you use, very good. As an experience Java developer could think Kotlin is just Java with syntax sugar, sometimes they could be wrong. Kotlin is very flexible language and in some cases you can shoot yourself in the foot. As the same simple getter written in 2 different ways will also behave differently.
Human Factors: Rejection
Among the technical difficulties also there could be human factors. When debating whether to use Kotlin or Java for development you should be aware that there’s a third option: use both.
And you can explain it to the colleagues and do not force them to use Kotlin, but simply let them stay with Java. It is possible to have Kotlin and Java classes side by side within the same project and everything will still compile.
Human Factors: Approaching
How would you convince people?
First avoid Kotlin is cool argument, instead focus on the language features that promote maintainability and safety, also remind people that it can be adopted incrementally. And it is a gradual evolution instead of revolution.
Overall Code Base
Now, a bit of statistic in out code base.
Java + Kotlin
Kotlin Tooling in 2020
Kotlin is built with Tooling in mind from the ground up. It is well integrated with IntelliJ Idea and Android Studio. Yet, refactorings are not as rich as for Java, but steadily improving in JetBrains IDEs. However, not much for other IDEs available. Certain vendor Lock-In inevitable as of today. Kotlin has a very good support in Maven and Gradle.
Kotlin built by company which produces Intellij Idea IDE, which has first class support of the language. More and more new open source libraries appear on gitHub, which written in Kotlin and to be used in Kotlin environment. Also, you can use existing Java libraries from Kotlin.
Conclusion
- There is no free lunch, but Kotlin tastes good
- Especially suitable for users of JetBrains IDEs
- Very good interop with Java and small footprint
- It is a good trade-off with significant advantages
- More safety, more solid design, less boilerplate
- Applicable for new and legacy codebase
Finally, Kotlin is an excellent piece of engineering. It’s a good trade of for less boilerplate, better design and most importantly it could be used in legacy code bases just a long side with Java.








