Migrating a Java Spring Microservice to Kotlin: Practical Steps and Pitfalls

This article shares a developer’s experience converting a Java Spring Boot microservice to Kotlin, covering why Kotlin is attractive, required Maven plugins and dependencies, code‑migration quirks such as open classes and @Jvm annotations, testing hurdles, and the final decision to revert to Java.

Tech Musings
Tech Musings
Tech Musings
Migrating a Java Spring Microservice to Kotlin: Practical Steps and Pitfalls

Why Consider Kotlin for a Java Project?

In early 2023 the author became curious about coroutines; while Go and C++ support them, Java does not yet have a stable implementation (Project Loom is still experimental). Kotlin, however, offers lightweight, stack‑less coroutines and is fully interoperable with Java, making it a viable alternative.

What Makes Kotlin Attractive?

Expressive and concise syntax reduces boilerplate.

Safer code through null‑safety and other language features.

Seamless inter‑operation with existing Java code.

Structured concurrency via coroutines simplifies asynchronous programming.

These benefits led the author to try Kotlin in a stable microservice with clear business logic.

Preparing the Project

The target module had a stable API, well‑defined workflow, and a moderate codebase—ideal for a language migration experiment.

Required Maven Configuration

Using IntelliJ IDEA’s conversion tool, Java files were transformed to Kotlin, and the following dependencies and plugins were added:

kotlin-stdlib-jdk8

kotlin-test-junit

kotlin-maven-plugin (with all‑open and no‑arg extensions)

Key Maven snippet:

<build>
    <plugins>
        <!-- other plugins -->
        <plugin>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-maven-plugin</artifactId>
            <version>${kotlin.version}</version>
            <executions>
                <execution>
                    <id>compile</id>
                    <phase>process-sources</phase>
                    <goals>
                        <goal>compile</goal>
                    </goals>
                </execution>
                <execution>
                    <id>test-compile</id>
                    <phase>process-test-sources</phase>
                    <goals>
                        <goal>test-compile</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <jvmTarget>${kotlin.compiler.jvmTarget}</jvmTarget>
                <args>
                    <arg>-Xjsr305=strict</arg>
                    <arg>-Xemit-jvm-type-annotations</arg>
                </args>
                <compilerPlugins>
                    <plugin>spring</plugin>
                    <plugin>no-arg</plugin>
                </compilerPlugins>
                <pluginOptions>
                    <option>no-arg:annotation=com.example.NoArg</option>
                </pluginOptions>
            </configuration>
            <dependencies>
                <dependency>
                    <groupId>org.jetbrains.kotlin</groupId>
                    <artifactId>kotlin-maven-noarg</artifactId>
                    <version>${kotlin.version}</version>
                </dependency>
                <dependency>
                    <groupId>org.jetbrains.kotlin</groupId>
                    <artifactId>kotlin-maven-allopen</artifactId>
                    <version>${kotlin.version}</version>
                </dependency>
            </dependencies>
        </plugin>
    </plugins>
</build>

Because the project mixes Java and Kotlin, the plugin execution order was adjusted so that the Kotlin compiler runs before the Java compiler.

Jackson is used for JSON handling, but its default version lacks full Kotlin data‑class support, so jackson-module-kotlin was added.

Common Code‑Conversion Pitfalls

1. Open Classes – Kotlin classes are final by default; to allow Spring proxies, the all-open plugin adds open automatically to annotated classes, while regular business classes need manual open.

2. Import Statements – Automatic conversion can scramble imports, requiring manual cleanup.

3. Exposing Fields – Kotlin generates getters/setters; adding @JvmField suppresses this and exposes the field directly, useful for val static final constants.

4. Static Methods – To call a Kotlin function from Java without an instance, annotate it with @JvmStatic. Example:

// Kotlin object
object Obj {
    @JvmStatic fun callStatic() {}
    fun callNonStatic() {}
}

// Java usage
Obj.callStatic(); // works
Obj.callNonStatic(); // compile error, need Obj.INSTANCE.callNonStatic();

Reflections on the Migration

The author noticed two main issues:

Readability – Kotlin’s expressive syntax can become hard to follow, especially with nested safe‑call operators and lambdas, making maintenance challenging for teammates unfamiliar with Kotlin.

Coroutine Suitability – Although the initial goal was to use coroutines, time constraints and the fact that underlying MySQL drivers remain blocking reduced the performance benefit, leading to postponement of coroutine adoption.

Testing also posed difficulties: JUnit works but has compatibility quirks with Kotlin. The author recommends mockk for mocking, while noting unresolved issues with PowerMock and Kotlin inline functions.

Final Decision

After a brief trial, the codebase was switched back to pure Java because the migration effort outweighed the productivity gains. Kotlin does reduce boilerplate and some classes of bugs, but the team decided to keep a unified Java stack for consistency.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Kotlinmavenunit testingSpring Bootcode migrationcoroutine
Tech Musings
Written by

Tech Musings

Capturing thoughts and reflections while coding.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.