How to create a Kotlin Multi-Module Project
Thomas
Posted on November 17, 2023
Spring Boot and Kotlin are a powerful combination for creating modern web applications. For Kotlin, we usually use Gradle with the Kotlin DSL developed for it. How can we turn such a project into a multi-module project?
Multi-module projects allow a monolith to be broken down into distinct modules without the need for the complex infrastructure required for microservices. The modules are not only separate folders similar to packages, but also form a dependency graph - thus circular connections are not possible. In the end, each module is compiled into a jar, and all jar's together form the executable Spring Boot fat jar.
The correct partitioning of the modules is crucial for an architecture that supports maintainability of the app and collaboration within the team in the long run. A few tips on this can be found in best practices for multi-module projects.
Splitting the classes and resources into the modules
We can create a simple Kotlin project in the current Spring Boot version 3.1.5
directly in the Bootify Builder. With open project we launch a project and select Kotlin as language. Gradle is already preset, and the database we can set to None for now. With this we can already explore and download the source code.
Initial version of the build.gradle.kts
Before we customize the build file, we first prepare the two modules kotlin-web
and kotlin-base
and create two folders with exactly these names. For the web module we move only the HomeController
to the new path ./kotlin-web/src/main/kotlin/io/bootify/kotlin/controller
. All other classes and resources are relevant for the whole application, so we move the whole src
folder to ./kotlin-base
.
include 'kotlin-base', 'kotlin-web'
Adding the new settings.gradle file
We also need a new file that provides a list of all our modules to Gradle. With this, the only thing missing at last is the customization of the build.gradle.kts
.
Customizing the build file
First we prepare the plugins. The version of Kotlin should always be derived from the Spring Boot version, as this ensures the highest possible compatibility - the newest Kotlin version is not tested with Spring Boot yet. The allprojects
part specifies a few global settings.
import io.spring.gradle.dependencymanagement.dsl.DependencyManagementExtension
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.springframework.boot.gradle.dsl.SpringBootExtension
import org.springframework.boot.gradle.tasks.run.BootRun
buildscript {
repositories {
mavenCentral()
}
}
plugins {
id("org.springframework.boot") version "3.1.5" apply false
id("io.spring.dependency-management") version "1.1.3" apply false
kotlin("jvm") version "1.8.22" apply false
kotlin("plugin.spring") version "1.8.22" apply false
}
allprojects {
tasks.withType<JavaCompile> {
sourceCompatibility = "17"
targetCompatibility = "17"
}
tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "17"
}
}
}
First part of our extended build file
In the following, the prepared plugins are applied to all submodules. Some settings like the location of the Spring Boot BOM and the main class are specified as well.
subprojects {
apply {
plugin("java-library")
plugin("io.spring.dependency-management")
plugin("org.jetbrains.kotlin.jvm")
plugin("org.jetbrains.kotlin.plugin.spring")
plugin("org.springframework.boot")
}
tasks.withType<Test> {
useJUnitPlatform()
}
repositories {
mavenCentral()
}
the<DependencyManagementExtension>().apply {
imports {
mavenBom(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)
}
}
extra.apply {
set("projectName", "${extra.get("rootProjectName")}-$project.name")
}
configure<SpringBootExtension> {
mainClass.set("io.bootify.kotlin.base.KotlinApplicationKt")
}
tasks.getByName<BootRun>("bootRun") {
enabled = false
}
}
Second part of our build file
In the last part we configure our actual modules. Here kotlin-base
is referenced by kotlin-web
. By integrating further depedencies as api
in kotlin-base
they are visible in kotlin-web
as well. With implementation
a depedency would be "invisible" within kotlin-web
during development, even if eventually all dependencies end up together in the fat jar.
project(":kotlin-web") {
apply {
plugin("application")
}
tasks.getByName<BootRun>("bootRun") {
environment.put("SPRING_PROFILES_ACTIVE", environment.get("SPRING_PROFILES_ACTIVE") ?: "local")
workingDir = rootProject.projectDir
enabled = true
}
dependencies {
val api by configurations
api(project(":kotlin-base"))
}
}
project(":kotlin-base") {
dependencies {
val api by configurations
api("org.springframework.boot:spring-boot-starter-web")
api("org.springframework.boot:spring-boot-starter-validation")
api("org.jetbrains.kotlin:kotlin-reflect")
api("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
}
}
Last part of our build file
The bootRun
task is only enabled for our kotlin-web
module and uses the root module as a working directory. This way all required files like docker-compose.yml
can be picked up from there.
With this we have already transformed our project into a multi-module project! With gradlew clean build
we can build our application as usual - which will compile and test each modules one after the other according to their dependency path.
Bootify offers an option in the Professional plan to create a multi-module project. This creates the structure described here, matching the other settings selected. In addition, integration tests are available and custom modules can be added, which are then already integrated into the build process.
Posted on November 17, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.