Gradle

An alternative to Apache Maven many see Gradle Build Tool as a step forward and a better tool, whilst others would suggest it is very good and powerful but overkill for simple Java build tasks.

If you want a good way to learn Gradle then I would recommend Gradle Build Tool Fundamentals | Pluralsight, which I appreciate is not free but it is good, if you already have access to Pluralsight | THE Technology Skills Platform. An alternative is to just study the documentation and use free resources, in which case head over to Gradle User Manual and look at the official guides.

If you use IntelliJ IDEA then Working with Gradle in IntelliJ IDEA (2021) - YouTube is a great starting point.

One of the conventions that Gradle uses over configuration is the Maven directory layout, which is documented at Maven – Introduction to the Standard Directory Layout.

How does Gradle work?

Fortunately Gradle have produced a series of blog posts explaining how it works.

In addition there is The Gradle Cookbook which is a very useful resource.

Plugins

The Java Plugin adds several tasks to the Gradle build and uses many of the same conventions as Maven, which is very helpful. Using Convention over Configuration means that if you stick to the conventions, you need very little configuration. It is worth highlighting that it has "child" plugins:

JaCoCo

The JaCoCo Plugin is often used with Java development, although it can be a little quirky and not always show the correct level of test coverage. When working with it though, I have found Barfuin / gradle-jacoco-log · GitLab very helpful.

Whilst the Java plugin is a "well known" plugin, that you can easily specify in your build file, other plugins are community ones, which need the full id to be specified and have a version associated with them, they are pulled in from the Gradle - Plugins portal.

Configuration

One issue that does occur, from time to time, is that Gradle does not fully support the very latest JDK, especially just after that JDK ships. This means you cannot use the latest JDK to run Gradle. However you can use toolchains to run Gradle on Java 11 for example but compile your solution on Java 17. Read Toolchains for JVM projects which explains how to do this.

While working on this I found the following helpful:
gradlew -q javaToolchains
This confirms whether auto detect and auto download are enabled and then lists all known/found JDKs. It is worth looking at the following: org.gradle.java.installations.auto-download, org.gradle.java.installations.auto-detect, org.gradle.java.installations.paths which can all be set in the gradle.properties file.

Tasks

Copy

The Copy - Gradle DSL Version 6.6 task, implements the CopySpec (Gradle API 6.6) interface and is a very powerful option. One method to look out for is expand, which can replace tokens in files as they are copied.

Configuration Scopes

We have "implementation", which covers both compileOnly and runtimeOnly, although we can use these directly. There is also testImplementation, which also has testCompileOnly and testRuntimeOnly but it does also get everything in implementation.

Running Gradle

There are a couple of useful options when running Gradle, adding -q puts it into quiet mode and -i gives more info.

It is well worth using gradle.properties file to specify dependency versions, these can be easily used in the main build script, see Writing Build Scripts for some details on this. This can also be done with a buildscript section but you'll need double quotes for the strings when using them.

Gradle Wrapper

The Gradle Wrapper is a good way to execute Gradle and it makes sure, when committed to your version control system, that everyone is using the same version of Gradle. This is important when you are using a build server or running your builds via automated pipelines.

It is easy to use the Gradle Wrapper to upgrade Gradle, which I have done with the following two commands:
./gradlew wrapper --gradle-version=6.6.1
./gradlew --version

I used this to upgrade from Gradle 6.6 to 6.6.1 and it was actually the second command the triggered the download and change.

Running Tests

It is important to start by reading Testing in Java & JVM projects, followed by Testing with JUnit5 Sample.

When working with JUnit it is worth examining Test - Gradle DSL Version 6.6 and specifically the "testLogging" events. If you want better looking output then consider using the community plugin Gradle - Plugin: com.adarshr.test-logger. With jUnit5 we need to add useJUnitPlatform() in the test block, as well as make a number of other changes.

It is worth adding that you can filter tests, and hence focus on what is needed.

Dependencies

This is a complex and interesting area, and clearly gets worse the more things you add. Howwever, it is a fundamental part of Gradle, so here are some things to note.

  • Take care with using the different options correctly, implementation, testImplementationtestRuntimeOnly etc, these are known as "configurations"
  • gradlew dependencies - will show all the dependency trees for all configurations
  • gradlew my-project:dependencies - this works when you have multiple projects in the build file
  • gradlew dependencies --configuration testRuntimeClasspath - this shows only the specified configuration, in this case "testRuntimeClasspath"
  • It is always good to comment dependencies out and see if it still works, sometimes with a large and complex tree common libraries get pulled in
  • Check if version numbers are needed, automatic versioning works well and is easier

Handy Commands

These are Gradle commands I have found useful and used in conjunction with other things found here

  • gradlew projects - list all the configured projects in the build file, handy for use with the dependencies option

Handy Statements

Output

I wanted to output a variable to check its value, but it was not as easy as I expected. I found three options that worked which are as follows
project.logger.warn("Build Directory: $buildDir")
println("Build Directory: $buildDir")
println "Build Directory: $buildDir"
What caught me out was that I started off with single quotes, which don't expand the variable, you need double quotes for variable expansion, and as you can see the brackets are optional.

Sometimes you don't want your "output" showing at the configuration phase, but you want to show something after a task has finished, in which case you will need to do something like this

myCustomTask {
    item {
        ...
    }
    doLast {
        println("This displays, once myCustomTask has finished")
    }
}

There is documentation on this at Build Lifecycle which is helpful.