1. Introduction
In this quick tutorial, we’ll see how to set up a Maven project handling both Java and Kotlin sources.
We’ll first create a project for Java sources only. We’ll then add the kotlin-maven-plugin to handle Kotlin as well.
And finally, we’ll add some dummy classes, package our application, and test if everything works as expected.
2. Create a Java Project with Maven
First of all, let’s create a straightforward Java project with Maven:
<artifactId>maven-java-kotlin</artifactId>
<packaging>jar</packaging>
<properties>
<java.version>11</java.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</build>
This pom file contains all we need to compile Java sources and package them into a jar file.
3. Add Kotlin Maven Plugin
Now we need to tune this pom file so that it can handle Kotlin sources as well.
Let’s first add kotlin.version to our properties and kotlin-stdlib to our dependencies. This way we’ll have access to Kotlin features:
<properties>
<java.version>11</java.version>
<kotlin.version>1.8.0</kotlin.version>
</properties>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
Then, we need to add the kotlin-maven-plugin to our Maven plugins.
We’ll configure it to handle both compile and test-compile goals, telling it where to find our sources.
By convention, we keep Java and Kotlin sources in different directories, though we could put them all in the same:
<plugin>
<artifactId>kotlin-maven-plugin</artifactId>
<groupId>org.jetbrains.kotlin</groupId>
<version>${kotlin.version}</version>
<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>
</plugin>
This is nearly the end of the configuration. We need to adapt maven-compiler-plugin configuration as we need Kotlin sources to be compiled before Java sources.
Frequently, Maven plugins executions happen according to the declaration order. So we should place maven-compiler-plugin after kotlin-maven-plugin. But the former has two specific executions that are executed before everything else during the phases: default-compile and default-testCompile.
We need to disable them and enable java-compile and java-test-compile instead to ensure that kotlin-maven-plugin execution will happen before maven-compiler-plugin:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
<executions>
<execution>
<id>default-compile</id>
<phase>none</phase>
</execution>
<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>
We can see that the default executions are disabled using phase none, and Java-specific executions are bound to compile and test-compile phases.
4. Say ‘Hello World!’ Both Java and Kotlin
Now that we have correctly set everything up let’s say hello to the world… from both Java and Kotlin.
To do that, let’s create an Application class with a main() method. This method will either call a Java or a Kotlin class according to its first argument:
public class Application {
static String JAVA = "java";
static String KOTLIN = "kotlin";
public static void main(String[] args) {
String language = args[0];
switch (language) {
case JAVA:
new JavaService().sayHello();
break;
case KOTLIN:
new KotlinService().sayHello();
break;
default:
// Do nothing
break;
}
}
}
The JavaService and KotlinService classes are simply saying “Hello World!”:
public class JavaService {
public void sayHello() {
System.out.println("Java says 'Hello World!'");
}
}
class KotlinService {
fun sayHello() {
System.out.println("Kotlin says 'Hello World!'")
}
}
We can now compile and package our application by calling mvn package command.
Let’s test the produced jar by running the following commands in a terminal:
java -cp maven-java-kotlin-1.0.0-SNAPSHOT.jar path.to.your.Class "java"
As we can see this calls the JavaService class which prints to the console “Java says ‘Hello World!'”.
java -cp maven-java-kotlin-1.0.0-SNAPSHOT.jar path.to.your.Class "kotlin"
And this one calls KotlinService class, which prints “Kotlin says ‘Hello World!'”.
5. Conclusion
In this article, we focused on how to create a Maven project handling both Java and Kotlin sources, compiling and packaging them into a jar.
The full code can be examined over on GitHub.