Article about advantages of using Gradle with Kotlin DSL.

Intro

Gradle is a build tool which is able to compile Java projects, however developers can also write custom tasks or perform additional complex actions by writing Gradle build scripts. Moreover, Gradle can also build C++ or Python projects. Similar to Maven, it is also possible to apply external plugins which can for example push docker image to registry or create bootable Jar containing Spring app.

Although Maven build system is still more popular, Gradle gains popularity.

Since version 5.0 Gradle now supports Kotlin DSL (alongside Groovy) as build scripts. It enables project’s code to be Kotlin only, so that both application code and build scripts are written in Kotlin language.

In the next sections there will be described sample Gradle noticeable features which can simplify and speed up developers work. Experiment will be based on Spring Boot Project originally generated by Initializr. Some changes were applied for the need of this post. Whole code can be found on Github.

Export logic and models to buildSrc module

Complex logic and structures can be defined outside build.gradle.kts to improve readability. Gradle provides buildSrc module for this purpose. This module’s code can be shared for every subproject. Before each Gradle build, module is automatically compiled, tested and exported in the classpath of build script. It can be good place for dependencies or plugins versions definitions like in buildSrc/src/main/kotlin/Versions.kt. After defining some const values, they can be used in build scripts to supply version strings (build.gradle.kts):

(...)
    kotlin("jvm") version Versions.kotlin
    kotlin("plugin.spring") version Versions.kotlin
(...)

Additionally, in buildSrc module, utility classes or even entire plugins can be located and shared amongst all subprojects. More info about this helpful module can be found in official documentation.

Applying external plugins

Spring Plugins

Code generated by Initializr let developer to build app, create bootable Jar or run on embedded local Tomcat server by applying Spring Boot plugin in build.gradle.kts:

(...)
	id("org.springframework.boot") version Versions.spring
(...)

This one line in build script create new tasks:

  • bootRun - run app locally
  • bootJar - pack app into single Jar file with all necessary dependencies

Additionally, this plugin can be paired with Dependency Management plugin which similar to Maven, can be used to simplify spring dependencies. After applying this plugin all defined Spring dependencies version are taken from Spring BOM with the same version as Spring Boot plugin:

(...)
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    testImplementation("org.springframework.boot:spring-boot-starter-test") {
        exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
    }
(...)

This way, there is no need to define versions if dependency is contained in related Spring BOM.

Release Plugin

In case of need to create new release of an app, there is a helpful plugin. It can automatically create tag and commits in Git repository and update version in gradle.properties. Additional behavior can be applied during release. Plugin creates two tasks: afterReleaseBuild and beforeReleaseBuild. These tasks can be attached by dependsOn() function to any other tasks, for example to publish Java lib to Maven repository or upload docker image to ECR docker registry. Some other less complex action can also be added, like updating version in project’s README.md file. It can be achieved by the following code in build.gradle.kts:

(...)
    val updateReadmeVersion by registering {
        group = "release"

        doFirst {
            logger.info("Updating application version in README.md file")
            file("$rootDir/README.md").replaceString(Regex("version: .*"), "version: $version")
        }
    }
    named("afterReleaseBuild") {
        dependsOn(updateReadmeVersion)
    }
(...)

Note that we are creating new task named updateReadmeVersion which is using File class extension function replaceString() defined in buildSrc/src/main/kotlin/FileUtis.kt file. This new task is then attached to afterReleaseBuild by dependsOn() method. Because of that, when afterReleaseBuild is invoked, updateReadmeVersion task is executed before.

Debugging

Since Gradle builds is configured by code, it is possible to debug each task as normal Java process. In Intellij Idea IDE it can be done as follows:

  1. Put a breakpoint in any project’s script

  2. Run desired task in debug mode

As it is presented in the screenshot above, it is now possible to watch variables or evaluate custom expressions which can be very beneficial when we would like to create a custom task, watch some project properties or develop our own Gradle plugin.

Summary

Personally, I worked with older build tools such as Maven or Ant and in my opinion Gradle is a way better. In comparison to mentioned tools, Gradle can boost programmer’s performance and simplify development related activities such as jar publishing, code inspections, new version release or even Kubernetes deployment.

Writing scripts in Kotlin code instead of XML elements can also improve readability and maintainability of build config. Developers can print out some configuration variables or even debug Kotlin DSL code. Such flexibility was not possible in case of older build systems.

Major drawback of using Gradle which comes to my mind, could be a build configuring time or Gradle Deamon start time in Intellij Idea IDE. It can sometimes exceed 30 seconds and can be annoying. Nevertheless, it is a rare case and Jetbrains team is working actively to solve problems.