How to add Kotlin as a second language to a SpringBoot app using Maven multimodule
The official Kotlin Documentation has a section on how to configure Maven so that it compiles Kotlin code next to Java code. But that setup assumes a configuration without Maven modules. Maybe it’s just me, but it took me quite some time to adapt that solution.
So this is what I learned:
Assumptions
You have a Java Project using Spring Boot which is seperated into several Maven modules. The parent pom of all modules is the pom-file in the base directory and the parent of that pom is spring-boot-starter-parent. Here’s a demo project configured just like that.
How to modify the main pom-file
- add the following properties:
<properties>
...
<java.version>21</java.version>
<kotlin.version>2.0.0</kotlin.version>
<kotlin.compiler.incremental>true</kotlin.compiler.incremental> <!-- optional: for faster builds -->
...
</properties>
The properties maven.compiler.source
and maven.compiler.target
can be removed - should they be present in your configuration -
since we’ll also update the configuration of the maven-compiler-plugin.
- prepare the dependencies for kotlin-reflect and kotlin-stdlib:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
<version>${kotlin.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
- prepare the following configuration of the kotlin-maven-plugin and the maven-compiler-plugin:
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<extensions>true</extensions>
<executions>
<execution>
<id>compile</id>
<!--<goals><goal>compile</goal></goals> not needed because of <extensions>true<.../>-->
<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> not needed because of <extensions>true<.../>-->
<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>
</compilerPlugins>
</configuration>
<dependencies>
<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.13.0</version>
<executions>
<!-- Replacing default-compile as it is treated specially by maven -->
<execution>
<id>default-compile</id>
<phase>none</phase>
</execution>
<!-- Replacing default-compile 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>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
How to modify all the module pom-files
first add kotlin-maven-plugin and the maven-compiler-plugin to all modules dependency list
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
</dependency>
...
</dependencies>
and then if you’re in a module which uses the spring-boot-maven-plugin, use this order:
<build>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
...
</plugins>
</build>
if you’re in a module without that plugin the order of the other two remains:
<build>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
...
</plugins>
</build>
The order of these plugins is important. Otherwise, your Kotlin code might see your Java code but not the other way around.
Now you can add a src/main/kotlin
-Path in your project and convert some of your Java classes to Kotlin.
And that’s already all there is to do to change your Java project to a Java and Kotlin project. You can check the Java and Kotlin config branch of my demo project.
Bonus: pure Kotlin configuration
So once all the source files have all been converted to Kotlin, can we simplify the Java&Kotlin configuration? Yes we can:
- remove the maven-compiler-plugin-config from all the pom-files
- remove the
<executions>
-block from the kotlin-maven-plugin in the main pom-file - configure
sourceDirectory
andtestSourceDirectory
at the start of thebuild
-block in the main pom-file
<build>
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
<pluginManagement>
...
</pluginManagement>
</build>
- keep the
<java.version>
in the properties block, the spring-boot-maven-plugin will still use it to compile the Kotlin code to the configured JVM bytecode version.
Here’s the Kotlin config branch of my demo project to double-check.
Minimum Kotlin versions for JDK21 support
When you check my demo project, you’ll see that I’ve used <java.version>21</java.version>
for all the different branches/configurations.
What is the minimum Kotlin version to support this:
mixed Java and Kotlin config
Your kotlin.version
has to be at least 1.9.20
, since Kotlin 1.9.20-Beta1
was the first version to support a jvmTarget of 21
.
pure Kotlin config
Your kotlin.version
has to be at least 2.0.0
in order to use JDK 21. For Kotlin versions 1.9.2(0-4)
an error is thrown, does look like this:
Caused by: java.lang.IllegalAccessError: superclass access check failed: class org.jetbrains.kotlin.kapt3.base.javac.KaptJavaCompiler (in unnamed module @0x17de6b) cannot access class com.sun.tools.javac.main.JavaCompiler (in module jdk.compiler) because module jdk.compiler does not export com.sun.tools.javac.main to unnamed module @0x17de6b
The bug
is fixed from Kotlin version 2.0.0-Beta1
onwards.