DEV Community

Thomas
Thomas

Posted on • Originally published at bootify.io

How to create a Kotlin Multi-Module Project

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'
Enter fullscreen mode Exit fullscreen mode

  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"
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

  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
    }
}
Enter fullscreen mode Exit fullscreen mode

  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")
    }
}
Enter fullscreen mode Exit fullscreen mode

  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.

» See Features and Pricing

Top comments (0)