Kotlin Gradle Kotlin DSL
Introduction
When working with Kotlin projects, you'll inevitably need to configure build settings. Gradle is a powerful build tool used for this purpose, and while traditionally Gradle build scripts were written in Groovy (using build.gradle
files), Kotlin developers now have a more natural option: Kotlin DSL for Gradle (using build.gradle.kts
files).
The Kotlin DSL for Gradle provides significant advantages for Kotlin developers:
- Type safety: Catch errors at compile-time rather than runtime
- Better IDE support: Code completion, navigation, and refactoring
- Kotlin language features: Use the same language for both your project and build scripts
In this guide, we'll explore how to use the Kotlin DSL with Gradle to create more maintainable and type-safe build scripts.
Converting from Groovy to Kotlin DSL
If you're familiar with Gradle's Groovy DSL, here's how a simple build.gradle
file would look and the equivalent build.gradle.kts
file:
Groovy DSL (build.gradle):
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.8.0'
id 'application'
}
repositories {
mavenCentral()
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib"
testImplementation "junit:junit:4.13.2"
}
application {
mainClassName = 'com.example.MainKt'
}
Kotlin DSL (build.gradle.kts):
plugins {
kotlin("jvm") version "1.8.0"
application
}
repositories {
mavenCentral()
}
dependencies {
implementation(kotlin("stdlib"))
testImplementation("junit:junit:4.13.2")
}
application {
mainClass.set("com.example.MainKt")
}
Notice the key differences:
- Quotation marks for strings
- Method call parentheses
- Use of
.set()
for property assignment - Different plugin syntax
Key Components of a Kotlin DSL Build Script
Plugins Block
The plugins block is where you specify which plugins your build will use:
plugins {
kotlin("jvm") version "1.8.0"
kotlin("plugin.spring") version "1.8.0"
id("org.springframework.boot") version "3.0.2"
}
Repositories
The repositories block defines where Gradle should look for dependencies:
repositories {
mavenCentral()
google()
maven {
url = uri("https://jitpack.io")
}
}
Dependencies
Dependencies define the external libraries your project needs:
dependencies {
implementation(kotlin("stdlib"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
testImplementation(kotlin("test"))
testImplementation("org.junit.jupiter:junit-jupiter:5.9.2")
// Platform dependencies example
implementation(platform("com.squareup.okhttp3:okhttp-bom:4.10.0"))
implementation("com.squareup.okhttp3:okhttp")
}
Tasks
Tasks define operations that Gradle can execute:
tasks {
test {
useJUnitPlatform()
}
withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
kotlinOptions {
jvmTarget = "17"
freeCompilerArgs = listOf("-Xjsr305=strict")
}
}
}
Advanced Features
Extension Functions
You can create extension functions to make your build scripts more readable:
fun DependencyHandlerScope.implementationAll(vararg dependencies: String) {
dependencies.forEach {
implementation(it)
}
}
dependencies {
implementationAll(
"org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4",
"org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0",
"io.ktor:ktor-client-core:2.2.3"
)
}
Type-Safe Project Access
Accessing other projects in a multi-project build becomes type-safe:
dependencies {
implementation(project(":shared"))
implementation(project(":api"))
}
Custom Task Creation
Creating custom tasks is more intuitive:
tasks.register<Copy>("copyDocs") {
from("docs")
into("build/docs")
include("**/*.md", "**/*.txt")
}
// To create a task with a custom task class
tasks.register<MyCustomTask>("generateReport") {
inputFile.set(file("data/input.csv"))
outputFile.set(file("build/reports/output.json"))
}
Real-World Example: Multi-Module Kotlin Project
Let's see a more complete example for a multi-module Kotlin project:
settings.gradle.kts:
rootProject.name = "my-kotlin-app"
include(":app", ":core", ":shared")
build.gradle.kts (root project):
buildscript {
repositories {
gradlePluginPortal()
mavenCentral()
}
dependencies {
classpath(kotlin("gradle-plugin", version = "1.8.0"))
}
}
allprojects {
repositories {
mavenCentral()
}
}
tasks.register("clean", Delete::class) {
delete(rootProject.buildDir)
}
core/build.gradle.kts:
plugins {
kotlin("jvm")
id("java-library")
}
dependencies {
api(kotlin("stdlib"))
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
testImplementation(kotlin("test-junit"))
}
java {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
app/build.gradle.kts:
plugins {
kotlin("jvm")
application
}
dependencies {
implementation(project(":core"))
implementation(project(":shared"))
implementation("com.squareup.retrofit2:retrofit:2.9.0")
testImplementation(kotlin("test-junit"))
}
application {
mainClass.set("com.example.app.MainKt")
}
Version Catalogs
One of the most powerful features of Gradle with Kotlin DSL is Version Catalogs, which allow you to centralize dependency management:
settings.gradle.kts:
dependencyResolutionManagement {
repositories {
mavenCentral()
}
versionCatalogs {
create("libs") {
version("kotlin", "1.8.0")
version("coroutines", "1.6.4")
library("kotlin-stdlib", "org.jetbrains.kotlin", "kotlin-stdlib").versionRef("kotlin")
library("kotlin-coroutines", "org.jetbrains.kotlinx", "kotlinx-coroutines-core").versionRef("coroutines")
plugin("kotlin-jvm", "org.jetbrains.kotlin.jvm").versionRef("kotlin")
}
}
}
Then in your module's build.gradle.kts:
plugins {
alias(libs.plugins.kotlin.jvm)
}
dependencies {
implementation(libs.kotlin.stdlib)
implementation(libs.kotlin.coroutines)
}
Common Issues and Solutions
Type Resolution Problems
If you see "Unresolved reference" errors, make sure to:
- Use the correct import statements
- Apply the necessary plugins
- Use the latest Gradle version
String vs. String Literal
In Kotlin DSL, you need to be careful with strings:
// This works
implementation("com.example:library:1.0.0")
// This might not work as expected in all contexts
implementation(com.example:library:1.0.0)
Using apply() vs. Direct Configuration
// Option 1: Direct configuration
plugins {
kotlin("jvm") version "1.8.0"
}
// Option 2: Using apply (legacy approach, not recommended for plugins block)
apply(plugin = "kotlin")
Summary
The Kotlin DSL for Gradle provides a more type-safe and IDE-friendly way to write build scripts for your Kotlin projects. Key benefits include:
- Better IDE support with code completion and navigation
- Compile-time checking of your build logic
- Consistency by using Kotlin throughout your project and build scripts
- Access to all Kotlin features in your build scripts
By mastering the Kotlin DSL, you can create more maintainable and robust build configurations that align perfectly with your Kotlin codebase.
Additional Resources
- Official Gradle Kotlin DSL Primer
- Kotlin DSL API Documentation
- Gradle Guides: Migrating build logic from Groovy to Kotlin
Exercises
- Convert a simple Groovy-based Gradle build script to Kotlin DSL
- Create a multi-module project with shared dependencies using Version Catalogs
- Write a custom task in Kotlin DSL that generates a report of all dependencies used in your project
- Configure different build variants (e.g., debug, release) using Kotlin DSL
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)