From Java to Kotlin. There and back again
Tags: java, kotlin, spring
Published: 2022-04-01 20:08:38

In this article, I would like to consider the problems and their solutions, which we encountered during the migration of our small microservice from Java to Kotlin. 

## Stack
- Java 11
- Spring Web MVC (в рамках Spring Boot)
- Spring Data JPA
- Map Struct
- Lombok
- Maven

## Beginning
Firstly, I would recommend anyone, who wants to put Kotlin in your project to start from tests. During this process, we configure almost all you need. You go through these steps:
- Configure the project
- Add necessary libraries
- Catch many errors
- And, finally, migrate a part of your test.

At the same time, the main code responsible for the business logic is not affected.

## Add kotlin
### Maven
As usual, we start from  https://start.spring.io/

Let's compare 2 pom.xml - one for Java and one for Kotlin.

- Add Kotlin version

```xml
<properties>
 ....
  <kotlin.version>1.5.31</kotlin.version>
</properties>

```

- Add the standard library for Kotlin and Jackson

```xml
<dependency>
  <groupId>com.fasterxml.jackson.module</groupId>
  <artifactId>jackson-module-kotlin</artifactId>
</dependency>
<dependency>
  <groupId>org.jetbrains.kotlin</groupId>
  <artifactId>kotlin-reflect</artifactId>
</dependency>
<dependency>
  <groupId>org.jetbrains.kotlin</groupId>
  <artifactId>kotlin-stdlib-jdk8</artifactId>
</dependency>
```
- Add build section
```xml
    <build>
        <sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
        <testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
        <plugins>
          .....
            <plugin>
                <groupId>org.jetbrains.kotlin</groupId>
                <artifactId>kotlin-maven-plugin</artifactId>
                <configuration>
                    <args>
                        <arg>-Xjsr305=strict</arg>
                    </args>
                    <compilerPlugins>
                        <plugin>spring</plugin>
                        <plugin>jpa</plugin>
                    </compilerPlugins>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>org.jetbrains.kotlin</groupId>
                        <artifactId>kotlin-maven-allopen</artifactId>
                        <version>${kotlin.version}</version>
                    </dependency>
                    <dependency>
                        <groupId>org.jetbrains.kotlin</groupId>
                        <artifactId>kotlin-maven-noarg</artifactId>
                        <version>${kotlin.version}</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>
```

More information about `kotlin-maven-plugin` you can find [here](https://kotlinlang.org/docs/maven.html)

But this configuration is not appropriate if you want to use Java with Kotlin. In this case you need to configure maven-compiler-plugin ([documentation](https://kotlinlang.org/docs/maven.html#compile-kotlin-and-java-sources))
```xml
<build>
  <plugins>
    .....
    <plugin>
      <groupId>org.jetbrains.kotlin</groupId>
      <artifactId>kotlin-maven-plugin</artifactId>
      <executions>
        <execution>
          <id>compile</id>
          <goals>
            <goal>compile</goal>
          </goals>
          <configuration>
            <sourceDirs>
              <sourceDir>${project.basedir}/src/main/kotlin</sourceDir>
              <sourceDir>${project.basedir}/src/main/java</sourceDir>
            </sourceDirs>
          </configuration>
        </execution>
        <execution>
          <id>test-compile</id>
          <goals> <goal>test-compile</goal> </goals>
          <configuration>
            <sourceDirs>
              <sourceDir>${project.basedir}/src/test/kotlin</sourceDir>
              <sourceDir>${project.basedir}/src/test/java</sourceDir>
            </sourceDirs>
          </configuration>
        </execution>
      </executions>
      <configuration>
        <args>
          <arg>-Xjsr305=strict</arg>
        </args>
        <compilerPlugins>
          <plugin>spring</plugin>
          <plugin>jpa</plugin>
        </compilerPlugins>
      </configuration>
      <dependencies>
        <dependency>
          <groupId>org.jetbrains.kotlin</groupId>
          <artifactId>kotlin-maven-allopen</artifactId>
          <version>${kotlin.version}</version>
        </dependency>
        <dependency>
          <groupId>org.jetbrains.kotlin</groupId>
          <artifactId>kotlin-maven-noarg</artifactId>
          <version>${kotlin.version}</version>
        </dependency>
      </dependencies>
    </plugin>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <executions>
        <!-- Replacing default-compile as it is treated specially by maven -->
        <execution>
          <id>default-compile</id>
          <phase>none</phase>
        </execution>
        <!-- Replacing default-testCompile as it is treated specially by maven -->
        <execution>
          <id>default-testCompile</id>
          <phase>none</phase>
        </execution>
        <execution>
          <id>java-compile</id>
          <phase>compile</phase>
          <goals>
            <goal>compile</goal>
          </goals>
        </execution>
        <execution>
          <id>java-test-compile</id>
          <phase>test-compile</phase>
          <goals>
            <goal>testCompile</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>
```

Some information about plugins `All-open` and `No-arg` you can find below.

## Kotlin plugins
Kotlin uses [compiler plugins](https://kotlinlang.org/docs/all-open-plugin.html) to change ast-tree during compilation. By default, spring-initializer adds 2 plugins: `all-open` and `no-arg`. Also, kapt and the plugin for Lombok are popular.

### all-open
By default, all classes in Kotlin are final and they can not be overridden, so Spring can't use them to create proxy-class. `All-open` plugin adds `open` to classes with specified annotations. 

For spring there is a pre-configured [kotlin-spring](https://kotlinlang.org/docs/all-open-plugin.html#spring-support) plugin. 
It works only with these annotations:
- @Component
- @Async
- @Transactional
- @Cacheable
- @SpringBootTest

If you want to work with your custom annotations, you need to configure `kotlin-allopen`.

Also, I would like to highlight, that there are no JPA annotations, and you have to add them if you use JPA repositories.

### No-arg
This plugin adds an empty constructor to each class. For JPA there is [kotlin-jpa](https://kotlinlang.org/docs/no-arg-plugin.html#jpa-support). It works with annotations:
- @Entity
- @Embeddable
- @MappedSuperclass

But it doesn't add `open` to these classes.

### Kapt
It is an adapter for annotation processors ([documentation](https://kotlinlang.org/docs/kapt.html)). It is used, for example, by mapstruct to generate code for mappers with Kotlin.

Example, how you can add kapt plugin:
```xml
<plugin>
  <groupId>org.jetbrains.kotlin</groupId>
  <artifactId>kotlin-maven-plugin</artifactId>
  <version>${kotlin.version}</version>
  <executions>
    <execution>
        <id>kapt</id>
        <goals>
            <goal>kapt</goal>
        </goals>
        <configuration>
            <sourceDirs>
                <sourceDir>src/main/kotlin</sourceDir>
                <sourceDir>src/main/java</sourceDir>
            </sourceDirs>
            <annotationProcessorPaths>
                <annotationProcessorPath>
                    <groupId>org.mapstruct</groupId>
                    <artifactId>mapstruct-processor</artifactId>
                    <version>${mapstruct.version}</version>
                </annotationProcessorPath>
            </annotationProcessorPaths>
        </configuration>
    </execution>
  ...
  </executions>
  ....
</plugin>
```

In my experience, this plugin can break your build, if you use  Lombok because the plugin can't parse java-file where Lombok methods are used.

### Lombok plugin
This plugin is used to cope with this problem, but it still has only beta versions and doesn't support `@Builder`

## Cook Spring
Spring is an awesome framework, and, of course, it supports 
 Kotlin. But there are some interesting features.

Some presentations:
- [from Jetbrains](https://blog.jetbrains.com/kotlin/2020/08/the-state-of-kotlin-support-in-spring/)
- [from Spring](https://www.youtube.com/watch?v=tt4cxR5onQU)

### Features:

- the main entry point with Kotlin:
```Kotlin
@SpringBootApplication
class DemoKotlinApplication

fun main(args: Array<String>) {
    runApplication<DemoKotlinApplication>(*args)
}
```

- some new extension-methods for Kotlin were added, like  [RestOperationsExtensions.kt](https://github.com/spring-projects/spring-framework/blob/main/spring-web/src/main/kotlin/org/springframework/web/client/RestOperationsExtensions.kt)

- it is recommended to use `val` arguments with constructor
```kotlin
@Component
class YourBean(
  private val mongoTemplate: MongoTemplate, 
  private val solrClient: SolrClient
)
```
but there are some other options, for example, you can use `latenin`:

```kotlin
@Component
class YourBean {

    @Autowired
    lateinit var mongoTemplate: MongoTemplate

    @Autowired
    lateinit var solrClient: SolrClient
}
```

It is similar to 
```Java
@Component
public class YourBean {
   @Autowired
   public MongoTemplate mongoTemplate;
   @Autowired
   public SolrClient solrClient;
}
```

Also, you can use injection with set-methods:
```Kotlin
    var hello: HelloService? = null
        @Autowired
        set(value) {
            field = value
            println("test")
        }
```

- You can create properties classes with `@ConstructorBinding`

```Kotlin
@ConfigurationProperties("test")
@ConstructorBinding
class TestConfig(
    val name:String
) 
```
or with `lateinit`

```Kotlin
@ConfigurationProperties("test")
class TestConfig {
    lateinit var name: String
}
```

- if you want to generate meta-information, you should configure `spring-boot-configuration-processor` with `kapt`

## Kotlin with Hibernate
The main errors and problems are described in the [Haulmont article](https://www.jpa-buddy.com/blog/best-practices-and-common-pitfalls/).

Once again, I will draw your attention that you need to configure `no-args` and `all-open` plugins and implement `hashCode` and `equals` methods.


## Kotlin with Jackson
You should add [Jackson Module Kotlin](https://github.com/FasterXML/jackson-module-kotlin) in your project. After that, you can't specify a type of object explicitly.

```Kotlin
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue

data class MyStateObject(val name: String, val age: Int)

...
val mapper = jacksonObjectMapper()

val state = mapper.readValue<MyStateObject>(json)
// or
val state: MyStateObject = mapper.readValue(json)
// or
myMemberWithType = mapper.readValue(json)
```

## Kotlin with MapStruct
MapStruct works through the annotation processor. Therefore, it is necessary to configure `kapt` correctly. At the same time, if models use Lombok annotations, then there could be problems with mappers.

```xml
<plugin>
  <groupId>org.jetbrains.kotlin</groupId>
  <artifactId>kotlin-maven-plugin</artifactId>
  <version>${kotlin.version}</version>
  <executions>
    <execution>
        <id>kapt</id>
        <goals>
            <goal>kapt</goal>
        </goals>
        <configuration>
            <sourceDirs>
                <sourceDir>src/main/kotlin</sourceDir>
                <sourceDir>src/main/java</sourceDir>
            </sourceDirs>
            <annotationProcessorPaths>
                <annotationProcessorPath>
                    <groupId>org.mapstruct</groupId>
                    <artifactId>mapstruct-processor</artifactId>
                    <version>${mapstruct.version}</version>
                </annotationProcessorPath>
            </annotationProcessorPaths>
        </configuration>
    </execution>
  ...
  </executions>
  ....
</plugin>
```

## Kotlin with Lombok

Honestly, it is a pain.
Of course, you can try to use [Lombok compiler plugin](https://kotlinlang.org/docs/lombok.html), but often if you use Kapt and Lombok at the same time, you may encounter many problems.
By default, kapt uses all annotation processors and disables their work with javac, but for lombok it doesn't work correctly and you need to disable this behavior.

```xml
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.5.1</version>
    <configuration>
        <source>1.8</source>
        <target>1.8</target>
        <annotationProcessorPaths>
            <annotationProcessorPath>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok.version}</version>
            </annotationProcessorPath>
        </annotationProcessorPaths>
    </configuration>
</plugin>
```
At the same time, Lombok works correctly with kapt only if the annotation processor, which is used by kapt, does not depend on Lombok. In our case, it was not true. It was the reason, why we had to translate the entire domain model to Kotlin in the first step. Also one of the options is to use Delombok.

## Kotlin with Mockito

Mockito does not work correctly with Kotlin types out of the box. Spring recommends using [Mockk](https://mockk.io/). There is also a special module for Mockito, which adds support for Kotlin - [Mockito-Kotlin](https://github.com/mockito/mockito-kotlin).

In our project we used Mockito-Kotlin. We found only one problem: you need to be careful, because many methods are duplicated in different modules, for example, `any()` will now be in 2 places - `org.mockito.kotlin` and `org.mockito.Mockito`.

## Kotlin and logging
We chose [kotlin-logging](https://github.com/MicroUtils/kotlin-logging). It is a really convenient library and you can use it like this:
```kotlin
import mu.KotlinLogging
private val logger = KotlinLogging.logger {} 
class FooWithLogging {
    val message = "world"
    fun bar() {
        logger.debug { "hello $message" }
    }
}
```
#Conclusions
I would like to finish the article with brief conclusions. Using Java and Kotlin together in one project requires additional settings, but almost everything is solved and we get the opportunity to use 2 languages in one project. The biggest problem for us was incompatibility Lombok and Kotlin.

## References

- [Kotlin references](https://kotlinlang.org/docs/home.html)
- [Video from Jetbrains. Kotlin and Spring
](https://blog.jetbrains.com/kotlin/2020/08/the-state-of-kotlin-support-in-spring/)
- [Video. Kotlin and Spring from Spring team
](https://www.youtube.com/watch?v=tt4cxR5onQU)
- [Kotlin and Spring. References](https://docs.spring.io/spring-framework/docs/current/reference/html/languages.html#kotlin-resources)
- [Kotlin and Spring Boot. References](https://docs.spring.io/spring-boot/docs/2.5.5/reference/htmlsingle/#features.kotlin)
- [MapStruct and Kotlin](https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-kotlin)
- [Jackson and Kotlin](https://github.com/FasterXML/jackson-module-kotlin)
- [Mockito and Kotlin
](https://github.com/mockito/mockito-kotlin)
- [kotlin-logging](https://github.com/MicroUtils/kotlin-logging)