Gradle Kotlin DSL Patterns
Root Build File (Multi-Module)
// settings.gradle.kts
rootProject.name = "my-app"
include(":core", ":api", ":worker", ":shared")
dependencyResolutionManagement {
repositories {
mavenCentral()
}
versionCatalogs {
create("libs") { from(files("gradle/libs.versions.toml")) }
}
}
Version Catalog (gradle/libs.versions.toml)
[versions]
kotlin = "2.0.0"
spring-boot = "3.3.0"
coroutines = "1.8.1"
ktor = "2.3.12"
kotest = "5.9.1"
[libraries]
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
spring-boot-starter-web = { module = "org.springframework.boot:spring-boot-starter-web", version.ref = "spring-boot" }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
kotest-runner = { module = "io.kotest:kotest-runner-junit5", version.ref = "kotest" }
[bundles]
ktor-server = ["ktor-server-core", "ktor-server-netty", "ktor-server-content-negotiation"]
testing = ["kotest-runner", "kotest-assertions", "mockk"]
[plugins]
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
spring-boot = { id = "org.springframework.boot", version.ref = "spring-boot" }
Module build.gradle.kts
plugins {
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.spring.boot)
kotlin("plugin.spring") version libs.versions.kotlin.get()
}
dependencies {
implementation(project(":shared"))
implementation(libs.spring.boot.starter.web)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.bundles.ktor.server)
testImplementation(libs.bundles.testing)
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
kotlin {
jvmToolchain(21)
compilerOptions {
freeCompilerArgs.addAll("-Xjsr305=strict", "-opt-in=kotlin.RequiresOptIn")
}
}
tasks.test {
useJUnitPlatform()
maxParallelForks = (Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(1)
}
Custom Task
tasks.register<Exec>("generateOpenApi") {
group = "documentation"
description = "Generate OpenAPI spec from running server"
commandLine("curl", "-o", "docs/openapi.yaml", "http://localhost:8080/v3/api-docs.yaml")
dependsOn(tasks.bootRun)
}
tasks.register("printVersion") {
doLast { println("Version: ${project.version}") }
}
// Task dependency
tasks.named("build") { dependsOn("generateOpenApi") }
Build Optimization
// gradle.properties
org.gradle.caching=true
org.gradle.parallel=true
org.gradle.daemon=true
org.gradle.configuration-cache=true
org.gradle.jvmargs=-Xmx4g -XX:+HeapDumpOnOutOfMemoryError
// Exclude test tasks from full build when not needed
tasks.withType<Test> {
onlyIf { System.getenv("SKIP_TESTS") == null }
}
// Avoid rerunning if inputs unchanged
tasks.register("generateSources") {
inputs.dir("src/main/proto")
outputs.dir("build/generated/source/proto")
doLast { /* generation logic */ }
}
Convention Plugin (buildSrc)
// buildSrc/src/main/kotlin/kotlin-library.gradle.kts
plugins {
kotlin("jvm")
id("jacoco")
}
kotlin { jvmToolchain(21) }
tasks.test {
useJUnitPlatform()
finalizedBy(tasks.jacocoTestReport)
}
tasks.jacocoTestReport {
reports { xml.required = true }
}
Key Rules
- Use version catalogs (
libs.versions.toml) — single source of truth for dependency versions
buildSrc convention plugins eliminate copy-paste across modules
- Enable
configuration-cache and parallel in gradle.properties for faster builds
- Use
alias(libs.plugins.xxx) syntax — it's type-safe and IDE-navigable
jvmToolchain(21) ensures consistent JVM version across all developers without system-level setup