Authors Top

If you have a few years of experience with the Kotlin language and server-side development, and you’re interested in sharing that experience with the community, have a look at our Contribution Guidelines.

1. Overview

Groovy is the default language for writing Gradle scripts. However, since Gradle version 5.0, we can write these scripts in Kotlin, too.

In this tutorial, we’ll look at how we can write Gradle scripts in Kotlin. We will also look at some advantages and disadvantages of using Kotlin DSL scripts.

2. How to Create a Kotlin DSL Script

To write Kotlin DSL scripts, we need to use Gradle version 5.0 or later. To activate the Kotlin DSL and get the best IDE support, we need to use the following extensions:

  • .gradle.kts instead of .gradle for our build scripts
  • .settings.gradle.kts instead of .settings.gradle for all settings scripts
  • .init.gradle.kts instead of .init.gradle for initialization scripts

3. How to Write a Basic Gradle Script for a Java Library

In this section, we’ll go through the different building blocks of a Gradle script written in Kotlin DSL script. We will also look at the differences compared to when writing the same script in Groovy DSL.

3.1. Applying Plugins

We can apply the java-library plugin, which is a core plugin:

plugins {
    `java-library`
}

Please note that we don’t need to specify the id function – unlike in Groovy DSL – for core plugins.

We can apply a community plugin by specifying the fully qualified plugin id and version:

plugins {
    id("org.flywaydb.flyway") version "8.0.2"
}

3.2. Declaring Dependencies

We can declare different types of dependencies using type-safe accessors:

dependencies {
    api("com.google.inject:guice:5.0.1")
    implementation("com.google.guava:guava:31.0-jre")
    testImplementation("org.junit.jupiter:junit-jupiter-api:5.6.0")
    testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
}

The important thing to note here is that the Kotlin DSL makes all four accessors available in the body of the Gradle script immediately after the plugins {} block. This means that when composing this script, we will have type-safe auto-completion in supported IDEs. This results in a fast and superior scripting experience.

3.3. Declaring Repositories

We can declare both built-in and custom repositories using type-safe accessors:

repositories {
    mavenCentral()
    maven {
        url = uri("https://maven.springframework.org/release")
    }
}

While resolving a dependency, Gradle will first check the maven-central repository followed by the springframework repository.

3.4. Configuring Source Sets

Let’s say we want to define a separate source set for our integration tests. The project layout is:

gradle-kotlin-dsl 
  ├── src 
  │    └── main 
  │    |    ├── java 
  │    |    │    ├── DefaultSorter.java
  │    |    │    ├── InMemoryRepository.java
  │    |    │    ├── Reporter.java
  │    |    │    ├── Repository.java
  │    |    │    ├── Sorter.java
  │    ├── test 
  │    |    ├── java 
  │    |    │    └── DefaultSorterTest.java
  │    |    │    └── InMemoryRepositoryTest.java
  │    |    │    └── ReporterTest.java
  │    └── integrationTest 
  │         └── java 
  │              └── ReporterIntegrationTest.java
  └── build.gradle

We can define the integrationTest source set as:

sourceSets {
    create("integrationTest") {
        compileClasspath += sourceSets.main.get().output
        runtimeClasspath += sourceSets.main.get().output
    }
}

With this setup, we create a Gradle task called compileIntegrationTestJava. Using this task, we can compile the source files inside the src/integrationTest/java directory.

3.5. Defining a Custom Gradle Task

We need a task to run the integration tests. We can create it using Kotlin DSL:

val integrationTest = task<Test>("integrationTest") {
    description = "Task to run integration tests"
    group = "verification"

    testClassesDirs = sourceSets["integrationTest"].output.classesDirs
    classpath = sourceSets["integrationTest"].runtimeClasspath
    shouldRunAfter("test")
}

Here, we created a custom task using the Kotlin extension function task that is attached to the Project object. This is different from the Groovy syntax because there we would use the TaskContainer object to create a custom task. This is possible in Kotlin DSL too, but it’s more verbose.

4. IDE Support

Since Kotlin is a statically typed language, unlike Groovy, IDEs can provide several useful features like auto-completion, source code navigation, refactoring support, and error highlighting for Gradle scripts. This means we can enjoy a similar coding experience as we normally do when writing application code in Kotlin or Java. Currently, IntelliJ IDEA and Android Studio provide the best support for Kotlin DSLs.

5. Limitations

The Kotlin DSL has some limitations compared to Groovy especially when comparing the speed of script compilation. There are some other uncommon limitations mentioned on the Gradle official website.

6. Conclusion

In this article, we learned how to compose Gradle scripts using the Kotlin DSL. We also looked at some differences between Gradle scripts written in Groovy and those written in Kotlin DSL.

As usual, all code examples are available over on GitHub.

Authors Bottom

If you have a few years of experience with the Kotlin language and server-side development, and you’re interested in sharing that experience with the community, have a look at our Contribution Guidelines.

Comments are closed on this article!