Skip to main content

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):

groovy
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):

kotlin
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:

kotlin
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:

kotlin
repositories {
mavenCentral()
google()
maven {
url = uri("https://jitpack.io")
}
}

Dependencies

Dependencies define the external libraries your project needs:

kotlin
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:

kotlin
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:

kotlin
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:

kotlin
dependencies {
implementation(project(":shared"))
implementation(project(":api"))
}

Custom Task Creation

Creating custom tasks is more intuitive:

kotlin
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:

kotlin
rootProject.name = "my-kotlin-app"

include(":app", ":core", ":shared")

build.gradle.kts (root project):

kotlin
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:

kotlin
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:

kotlin
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:

kotlin
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:

kotlin
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:

  1. Use the correct import statements
  2. Apply the necessary plugins
  3. Use the latest Gradle version

String vs. String Literal

In Kotlin DSL, you need to be careful with strings:

kotlin
// 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

kotlin
// 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

Exercises

  1. Convert a simple Groovy-based Gradle build script to Kotlin DSL
  2. Create a multi-module project with shared dependencies using Version Catalogs
  3. Write a custom task in Kotlin DSL that generates a report of all dependencies used in your project
  4. 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! :)