Trying Kotlin in a Java project
One campus backend was built with Java and Spring Boot—a familiar and mature stack. Since Kotlin interoperates with Java, I decided to add a little Kotlin and enjoy some conveniences of a more modern language.
Everything worked in IDEA
For a quick experiment, I wrote a Kotlin controller:
@RestController
@RequestMapping("/test")
class HelloController {
@GetMapping("/hello")
fun hello(): String {
return "Hello, Kotlin!"
}
}
The application started normally from IDEA, and http://localhost:8080/test/hello returned Hello, Kotlin!.
The deployed endpoint returned 404
I pushed the branch and let CI/CD deploy it. On the server, the same endpoint returned 404 Not Found.
The pipeline itself had succeeded, but it used ./mvnw clean package, so I suspected that IDEA and Maven were building the mixed-language project differently.
The Kotlin class was missing from the Maven package
I reproduced the package locally:
mvn clean package
jar tf target/xxx.jar | grep "HelloController"
HelloController was absent. I had not explicitly configured the Kotlin source path, so Maven’s Kotlin compiler had not compiled the new code at all.
Mixed compilation order is not automatic
The original Kotlin plugin configuration looked reasonable at first:
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>2.2.21</version>
<configuration>
<jvmTarget>21</jvmTarget>
<compilerPlugins><plugin>spring</plugin></compilerPlugins>
</configuration>
<executions>
<execution>
<id>compile</id>
<goals><goal>compile</goal></goals>
<!-- No explicit phase -->
</execution>
</executions>
</plugin>
But binding both Java and Kotlin work to compile can leave the order ambiguous.
javac can consume compiled .class files, but it does not understand .kt source files. kotlinc, by contrast, can inspect Java source while compiling Kotlin.
That gives mixed projects two cases:
- Kotlin references Java: Kotlin can inspect the Java source.
- Java references Kotlin: Kotlin must already have produced the required
.classfiles.
To support both directions, the Maven lifecycle needs an explicit order.
Compile Kotlin before Java
I moved Kotlin compilation to process-sources, kept Java compilation in compile, and declared both source directories:
<build>
<sourceDirectory>${project.basedir}/src/main/java</sourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/java</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>2.2.21</version>
<configuration>
<jvmTarget>21</jvmTarget>
<sourceDirs>
<sourceDir>${project.basedir}/src/main/kotlin</sourceDir>
<sourceDir>${project.basedir}/src/main/java</sourceDir>
</sourceDirs>
<compilerPlugins>
<plugin>spring</plugin>
<plugin>lombok</plugin>
</compilerPlugins>
</configuration>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-allopen</artifactId>
<version>2.2.21</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-lombok</artifactId>
<version>2.2.21</version>
</dependency>
</dependencies>
<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>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>21</source>
<target>21</target>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
The lifecycle now had a predictable shape:
| Phase | Broken configuration | Correct configuration |
|---|---|---|
process-sources |
No action | Kotlin compiles and produces .class files |
compile |
Java and Kotlin can overlap or run in the wrong order | Java compiles against Kotlin classes |
| Result | Classes can be missing | All classes are packaged |
Verify the artifact, not only the IDE
After rebuilding, the Kotlin classes appeared in the JAR. The lesson was straightforward: IDE success does not prove that the CI build lifecycle is correct. In a mixed Java/Kotlin Maven project, source directories and compilation order should be explicit, and the final artifact should be inspected at least once.