Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Java  Module 14 – Testing, build tools and code quality

Gradle: Kotlin DSL, toolchains and tasks

Build a Java project with Gradle: write the Kotlin DSL build script, compile and test with a Java 25 toolchain, run tasks and commit the wrapper.

  • Intermediate
  • 22 minutes
  • Examples run with Java 25.0.4 and Python 3.14.8
  • By MySmartCoPilot
Free core, Pro depth: 7 of 22 minutes free
Free: Maven and Gradle, side by side · The build script · Tasks form a graph · 2 more sections. With a pass: 3 in-depth parts and 3 interview answers, about 15 minutes.

What you will learn

  • Create a Java project with Gradle's Kotlin DSL
  • Configure a Java toolchain so that Gradle compiles and tests with JDK 25 whatever JDK runs Gradle
  • Run, test and package a project with Gradle tasks and the wrapper
  • Convert a Maven build to Gradle and check that its tests give the same results

Before you start

Gradle is a build tool that models a build as a graph of tasks. A build script, build.gradle.kts in Gradle’s Kotlin DSL, applies plugins that add tasks: the java plugin adds compileJava, test, jar and build, and asking for build runs every task it depends on, in order. Gradle skips a task whose inputs and outputs have not changed since its last run, which keeps repeated builds fast. A toolchain names the Java version that compiles and tests the code, separately from the JDK that runs Gradle itself, and the wrapper, gradlew, fixes the Gradle version for everyone who builds the project. Android apps are built with Gradle, and so are many large Java projects.

This lesson ports the Maven exercise pack, an invoice library with four JUnit tests, to Gradle 9.7.1. Gradle runs on Java 21 here, while the toolchain compiles and tests with Eclipse Temurin 25.0.4.1. Gradle 9.8.1 is the newest release; the wrapper section shows how a project moves to it.

Maven and Gradle, side by side

Maven Gradle
pom.xml settings.gradle.kts and build.gradle.kts
Phases, with goals bound by the packaging Tasks, added by plugins and linked by dependencies
mvn package gradle build, or gradle jar for the JAR alone
Scopes compile, provided, runtime and test Configurations implementation, compileOnly, runtimeOnly and testImplementation
maven.compiler.release A Java toolchain, or the compiler’s release option
~/.m2/repository ~/.gradle/caches
mvnw gradlew

The scope row follows Gradle’s guide to migrating from Maven, which maps each scope to the configuration that replaces it.

The build script

The exercise pack's Gradle build

invoice-tools/settings.gradle.kts

// The build's name, and the projects it contains: here only the root project.
rootProject.name = "invoice-tools"

invoice-tools/build.gradle.kts

// The exercise pack built with Gradle's Kotlin DSL: the same sources and tests as the Maven version.
plugins {
    // Adds the tasks that compile, test and package Java code (compileJava, test, jar, build ...).
    java
}

group = "com.example"
version = "1.0.0"

java {
    // Compile, test and run with a Java 25 toolchain, whatever JDK runs Gradle itself.
    toolchain {
        languageVersion = JavaLanguageVersion.of(25)
    }
}

repositories {
    mavenCentral()
}

dependencies {
    // The JUnit BOM fixes the versions of every JUnit artifact; the platform launcher runs the tests.
    testImplementation(platform("org.junit:junit-bom:6.1.3"))
    testImplementation("org.junit.jupiter:junit-jupiter")
    testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

tasks.test {
    useJUnitPlatform()
    // Print each test's result in the build output.
    testLogging {
        events("passed", "skipped", "failed")
    }
}

invoice-tools/gradle.properties

# Where Gradle finds JDKs for toolchains: the folder named by the JDK_25 environment variable, and nowhere else, so
# the build uses the same JDK on every computer that sets it. Without these lines Gradle also searches the usual
# install locations.
org.gradle.java.installations.fromEnv=JDK_25
org.gradle.java.installations.auto-detect=false
org.gradle.java.installations.auto-download=false

The settings file names the build and its projects. The build script applies the java plugin, sets the coordinates, asks for a Java 25 toolchain and declares Maven Central as the repository. Its three test dependencies are the JUnit BOM, which fixes the version of every JUnit artifact, JUnit Jupiter, and the platform launcher that Gradle uses to start JUnit. The test block runs the tests on the JUnit Platform and prints each result. The gradle.properties lines tell Gradle to take its JDKs from the JDK_25 environment variable only, so the build finds the same JDK on every computer that sets it.

Properties ↔ YAML Converter (Spring Boot) Turn a gradle.properties file into YAML and back, to compare the settings of two builds side by side.

Tasks form a graph

The tasks gradle build runs: compileJava and classes, then jar and assemble on one branch, tests and check on the other.compileJavawith the toolchainclassesjarassemblecompileTestJavatestClassestestruns JUnitcheckbuild

The task graph of gradle build in a Java project

Text description of the diagram

The diagram shows the tasks that the java plugin adds and that gradle build runs, with an arrow from each task to a task that runs after it and needs its result.

  1. compileJava compiles src/main/java with the toolchain's compiler, and classes collects the compiled main classes.
  2. From classes, one branch builds the JAR: jar writes build/libs/invoice-tools-1.0.0.jar, and assemble is the lifecycle task for everything the project produces.
  3. The other branch tests: compileTestJava compiles src/test/java against the main classes, testClasses collects them, test runs JUnit, and check is the lifecycle task for every verification.
  4. build depends on both assemble and check, so it runs both branches.

The two resource tasks, processResources and processTestResources, also run before classes and testClasses. They are left out of the diagram because this project has no resources to copy, which the build log reports as NO-SOURCE.

gradle build on the exercise pack Python · build_session.py
from session import run

# Gradle runs on Java 21 (JAVA_HOME); the build's toolchain compiles and tests with Java 25. The first build of a
# project also compiles its Kotlin build script and downloads JUnit.
run(
    """
gradle --version | grep -E "^(Gradle|Launcher JVM|Daemon JVM)"
gradle build
ls build/libs build/test-results/test
""",
    folder="invoice-tools",
    show=("java", "gradle"),
    java_home="21",
)

Output

# openjdk version "25.0.4.1" LTS · Gradle 9.7.1 · JAVA_HOME: Java 21.0.12
$ gradle --version | grep -E "^(Gradle|Launcher JVM|Daemon JVM)"
Gradle 9.7.1
Launcher JVM:  21.0.12.1 (Eclipse Adoptium 21.0.12.1+1-LTS)
Daemon JVM:    …/installs/java/temurin-21.0.12+101.0.LTS (no Daemon JVM specified, using current Java home)
$ gradle build
> Task :compileJava
> Task :processResources NO-SOURCE
> Task :classes
> Task :jar
> Task :assemble
> Task :compileTestJava
> Task :processTestResources NO-SOURCE
> Task :testClasses

> Task :test

InvoiceTest > rejectsAZeroQuantity() PASSED

InvoiceTest > totalsTheInvoice() PASSED

InvoiceTest > roundsTaxHalfUpToTwoPlaces() PASSED

InvoiceTest > addsTaxToEachLine() PASSED

> Task :check
> Task :build

BUILD SUCCESSFUL in …
4 actionable tasks: 4 executed
Consider enabling configuration cache to speed up this build: https://docs.gradle.org/9.7.1/userguide/configuration_cache_enabling.html
$ ls build/libs build/test-results/test
build/libs:
invoice-tools-1.0.0.jar

build/test-results/test:
binary
TEST-com.example.invoice.InvoiceTest.xml

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 build_session.py

Gradle itself ran on Java 21, as its launcher and daemon lines show, while the toolchain compiled and tested with Java 25. The build passed through 11 tasks in the graph’s order. Four of them did real work, compileJava, jar, compileTestJava and test, which is what “4 actionable tasks: 4 executed” counts; the others group tasks or had no sources, marked NO-SOURCE. The 4 tests are the Maven version’s tests, and they passed again. The JAR landed in build/libs and the test results in build/test-results/test, where Maven used target. The last line of the build suggests the configuration cache, which reuses the result of evaluating the build scripts in later builds.

The order came from the graph, not from the build script: the script never mentions compileTestJava, and Gradle still ran it after classes, because the test classes need the main classes to compile. Asking for a single task runs only the part of the graph it needs, so gradle jar compiles the main code and stops without running a test, and gradle tasks lists every task the plugins added.

Four ideas to know by name

  • Kotlin DSL. build.gradle.kts is Kotlin code that the IDE checks as you type; the Groovy DSL, build.gradle, drives the same API.
  • Toolchain. The JDK that compiles, tests and runs the code, chosen by Java version from the JDKs Gradle knows.
  • Wrapper. gradlew, gradlew.bat and the gradle/wrapper folder, committed so that ./gradlew runs the project’s Gradle version.
  • Up-to-date checks. A task that declares its inputs and outputs is skipped when neither has changed.

Once a project has the wrapper, every command in this lesson becomes ./gradlew followed by the same task names, which is the form a build server should use. Android projects use the same wrapper, tasks and Kotlin DSL through the Android Gradle plugin, so these four ideas carry over to Android builds.

Check yourself

Check yourself

4 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 4 You run gradle build in a project that applies the java plugin. What happens?

    Choose one answer.

    Show the answer to question 1

    Answer: Gradle runs every task that build depends on, such as compileJava, test and jar, in dependency order

    build is a lifecycle task that depends on assemble and check, which depend on jar and test, and so on down to compileJava. The recorded build passed through 11 tasks of that graph; javadoc is not part of it.

  2. Question 2 of 4 The recorded gradle build printed "4 actionable tasks: 4 executed". How many tasks of the graph did it pass through in total?

    Type a number.

    Show the answer to question 2

    Answer: 11 tasks

    compileJava, processResources, classes, jar, assemble, compileTestJava, processTestResources, testClasses, test, check and build. Only compileJava, jar, compileTestJava and test have actions; the rest group other tasks or had no sources.

  3. Question 3 of 4 JAVA_HOME points to Java 21, and the build asks for a Java 25 toolchain. Which Java compiles the code?

    Choose one answer.

    Show the answer to question 3

    Answer: Java 25, the toolchain; Java 21 only runs Gradle itself

    The toolchain decides which JDK compiles and tests the code. In the recorded build, Gradle's launcher and daemon ran on Java 21 while compileJava and test used Java 25.

  4. Question 4 of 4 Which Gradle configuration replaces a Maven dependency in scope provided?

    Choose one answer.

    Show the answer to question 4

    Answer: compileOnly

    Gradle's guide to migrating from Maven maps provided to compileOnly: on the compile class path, but not on the runtime class path.

Toolchains in depth: one JDK runs Gradle, another compiles and tests Pro: opens with a Pro, Premium or Ultimate pass

Gradle runs on one JDK and builds with another, and confusing the two causes most builds that pass on a laptop and fail on a build server. This part shows how Gradle finds toolchains and what it does when it cannot.

In this part:

  • Which JDK does what, measured on this build
  • A toolchain Gradle cannot find, reproduced
  • The compiler’s release option, with a single JDK

1 runnable example (Python) · about 2 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Up-to-date checks measured: what Gradle skips, and a task that declares its inputs Pro: opens with a Pro, Premium or Ultimate pass

Gradle decides whether to run a task by comparing its inputs and outputs with those of its last run. This part measures that decision on the exercise pack, then writes a task that takes part in it.

In this part:

  • The same build twice, then a change that leaves the bytecode alone
  • A task of our own, with declared inputs and outputs
  • What each task outcome means

2 runnable examples (Kotlin, Python) · about 2 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

From Maven to Gradle: the converted pack, what init missed, and identical test results Pro: opens with a Pro, Premium or Ultimate pass

gradle init can convert a POM, and its output is a starting point rather than a finished build. This part converts the exercise pack, fixes what the conversion missed and checks that the tests give the same results.

In this part:

  • What gradle init wrote, and its first build
  • Four corrections, then the same four tests
  • The wrapper files, and moving to a new Gradle version
  • Exercise: port the pack by hand

2 runnable examples (Kotlin, Python) · about 3 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

  1. Fresher · Warm-up

    What does the Gradle wrapper do, and which of its files do you commit?

    The wrapper is a pair of scripts, gradlew for Linux and macOS and gradlew.bat for Windows, that runs the Gradle version the project names, downloading it first if this computer does not have it. The version and the download address are in gradle/wrapper/gradle-wrapper.properties, and gradle/wrapper/gradle-wrapper.jar holds the small program that does the download. Commit all four files, so that every developer and every build server runs ./gradlew build with the same Gradle, without installing Gradle by hand. To move to a newer version, run ./gradlew wrapper --gradle-version followed by the version, and commit the changed files; a SHA-256 checksum in the properties file makes the wrapper verify the download.

  2. Fresher to mid

    You add a Java 25 toolchain to a Gradle build. It works on every laptop, but CI fails with "Cannot find a Java installation on your machine matching languageVersion=25". The CI image has only Java 21. What do you do, and how do you keep the laptops and CI on the same JDK afterwards?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Mid to senior

    A custom task that generates source code from a schema runs on every Gradle build, even when nothing changed, and adds about 40 seconds to each build. How do you find out why Gradle never skips it, and how do you fix it?

    Model answer, Pro. First step and follow-ups

  4. Mid to senior

    Your team converted a Maven project to Gradle with gradle init. Before CI switches over, how do you show that the Gradle build produces the same thing as the Maven build, and what differences do you expect to find?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • A Gradle build is a graph of tasks; build ran 11 tasks, 4 of which did real work.
  • The toolchain decides which Java compiles and tests the code, whatever JDK runs Gradle itself.
  • Maven scopes become configurations: implementation, compileOnly, runtimeOnly and testImplementation.
  • Commit the wrapper so that everyone runs the same Gradle version with ./gradlew.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.