← zurück zum Blog

Spring Boot: Migration von Java zu Kotlin

Die moderne Programmiersprache Kotlin wird auch für die klassische Backend-Entwicklung immer interessanter. Diese Anleitung zeigt auf, wie sich eine Spring Boot Anwendung in wenigen Schritten von Java zu Kotlin migrieren lässt.
Stefan Sauterleute
Stefan Sauterleute13. August 2020

Einführung

Dieser Artikel zeigt auf, wie sich ein existierendes Java Spring Boot Projekt in ein Kotlin Spring Boot Projekt überführen lässt. Dabei kann das Projekt schrittweise migriert werden. Dies ermöglicht Kotlin durch seine 100 % Interoperabilität zu Java. Entwickler können somit neue Funktionen in Kotlin schreiben und auf den bestehenden Java-Code zugreifen. Den Entwicklern steht zudem frei, ob bestehender Java-Code in Kotlin überführt werden soll. Hierfür zeigt dieser Blogeintrag auf, wie mithilfe der IDE IntelliJ IDEA aus Java Code Kotlin Code generiert werden kann.

Ziel-Projektstruktur

Da wir ein gemischtes Projekt aus Java und Kotlin haben möchten sieht die Ziel-Projektstruktur wie folgt aus:

1
2
3
4
5
6
7
8
9
10
11
.
|-- "pom.xml"
|-- "src"
|   |-- "main"
|   |   |-- "java"
|   |   |-- "kotlin"
|   |   `-- "resources"
|   `-- "test"
|       |-- "java"
|       |-- "kotlin"
|       `-- "resources"

Java-Code behält wie gewohnt den “java” Ordner. Zusätzlich hinzu kommt der Ordner “kotlin”, der in src/main und in src/test eingeordnet wird. Benötigte Ressourcen werden wie gewohnt in src/main/resources bzw. src/test/resources abgelegt.

Kotlin-Integration

Kotlin / Java Version

In diesem Beispiel wird Kotlin in der Version 1.3.61, sowie Java 8 genutzt.

1
2
3
4
<properties>
    <java.version>8</java.version>
    <kotlin.version>1.3.61</kotlin.version>
</properties>

Erweiterung um Kotlin Abhängigkeiten

Dem Projekt werden diverse Kotlin Abhängigkeiten hinzugefügt.

  • Spring Boot bzw. Spring Framework 5 benötigt die Kotlin Standard Library. Da in diesem Projekt Java 8 verwendet wird, wird kotlin-stdlib-jdk8 als Abhängigkeit hinzugefügt
  • Zusätzlich benötgit das Spring Framework 5 kotlin-reflect
  • In Projekten mit Jackson wird für die Serialisierung und Deserialisierung von Kotlin Klassen das jackson-module-kotlin benötigt
1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- Kotlin-Erweiterungen -->
<dependency>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-stdlib-jdk8</artifactId>
</dependency>
<dependency>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-reflect</artifactId>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.module</groupId>
    <artifactId>jackson-module-kotlin</artifactId>
</dependency>

Kotlin Maven Plugin

Neben den Abhängigkeiten wird das Projekt noch mit diversen Plugins versehen. So wird das Spring Boot Projekt mit dem Spring Compiler Plugin versehen. Dies wird benötigt, da in Kotlin standardmäßig alle Klassen final sind. Mit Hilfe dieses Compiler Plugins werden Kotlin Klassen auf “open” gesetzt, was in Java einer nicht “final Klasse” entspricht.

Projekte die JPA verwenden sollten zusätzlich das JPA Kotlin Plugin aktivieren. Dies wird benötigt um Non-Argument-Konstruktoren für @Entity, @MappedSupperclass und @Embeddable Klassen generieren zu lassen.

Da unser Projekt Java und Kotlin unterstützen soll und diese sauber getrennt in eigenen Source-Folder abgelegt werden, muss Maven entsprechend für diese Source-Folder konfiguriert werden.

Des weiteren wird der Default Compiler von Maven überschrieben. Damit wird ermöglicht, dass der Kotlin-Compiler vor dem Java-Compiler ausgeführt wird.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
<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>
        <jvmTarget>${java.version}</jvmTarget>
    </configuration>
    <executions>
        <execution>
            <id>compile</id>
            <goals>
                <goal>compile</goal>
            </goals>
            <configuration>
                <sourceDirs>
                    <sourceDir>src/main/kotlin</sourceDir>
                    <sourceDir>src/main/java</sourceDir>
                </sourceDirs>
            </configuration>
        </execution>
        <execution>
            <id>test-compile</id>
            <goals>
                <goal>test-compile</goal>
            </goals>
            <configuration>
                <sourceDirs>
                    <sourceDir>src/test/kotlin</sourceDir>
                    <sourceDir>src/test/java</sourceDir>
                </sourceDirs>
            </configuration>
        </execution>
    </executions>
    <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>
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.5.1</version>
    <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>

Migration von Java zu Kotlin Dateien

IntelliJ IDEA ermöglicht es, Java-Dateien in Kotlin-Dateien zu konvertieren. Über den Shortcut Ctrl+Shift+Alt+K (Win/Linux) bzw. ⌥+⇧+⌘+K (MacOS) wird so aus einer Java-Datei eine Kotlin-Datei (siehe Dokumentation).

Es fällt allerdings auf, dass der generierte Kotlin-Programmcode nicht wie erwartet konvertiert wird. Wie die nachfolgende beiden Beispiele aufzeigen:

Beispiel 1 - Spring Boot Main

Original:

1
2
3
4
5
6
7
8
9
10
11
12
13
package de.byteleaf.example.springbootkotlinexample;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBootKotlinExampleApplication {

	public static void main(String[] args) {
		SpringApplication.run(SpringBootKotlinExampleApplication.class, args);
	}

}

Ergebnis der Generierung:

1
2
3
4
5
6
7
8
9
10
package de.byteleaf.example.springbootkotlinexample

import org.springframework.boot.SpringApplication

object SpringBootKotlinExampleApplication {
    @JvmStatic
    fun main(args: Array<String>) {
        SpringApplication.run(SpringBootKotlinExampleApplication::class.java, *args)
    }
}

Erwartetes Ergebnis:

1
2
3
4
5
6
7
8
9
10
11
package de.byteleaf.example.springbootkotlinexample

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

@SpringBootApplication
class SpringBootKotlinExampleApplication

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

Beispiel 2 - Datenklasse / POJO

Original:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package de.byteleaf.example.springbootkotlinexample.pojo;

public class DataClass {

    private String value;

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }
}

Ergebnis der Generierung:

1
2
3
4
5
package de.byteleaf.example.springbootkotlinexample.pojo

class DataClass {
    var value: String? = null
}

Erwartetes Ergebnis:

1
2
3
package de.byteleaf.example.springbootkotlinexample.pojo

data class DataClass(val value: String?)

Fazit

Ein bestehendes Spring Boot Projekt lässt sich gut in ein Java / Kotlin Spring Boot Projekt ändern. Entwickler können somit Kotlin nicht nur in neuen, sondern auch in bestehende Spring Boot Projekte nutzen und damit über moderne Sprachkonstrukte verfügen. Durch die von IntelliJ bereitgestellt Java-zu-Kotlin-Konvertierung kann bestehender Java-Code in Kotlin-Code konvertiert werden. Allerdings ist der generierte Programmcode oftmals nicht so wie man es erwarten würde, sodass es doch besser sein kann die Code-Tranformation manuell vorzunehmen. Durch die Interoprabilität besteht jedoch keinen Zwang, sodass der Entwickler frei entscheiden kann, ob eine Datei schlussendlich zu einem Kotlin-Code geändert werden soll.

© 2020 - Code: byteleaf - Design: Michael Motzek