Gradle: Installation

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Gradle 9.8.1 (released on 7 October 2026), with 8.14.6, the last 8.x release, mentioned where it differs. Every example marked as run was executed with the 9.8.1 binary distribution on JDK 26 (Oracle java version "26") on 2026-10-16, on macOS (Apple silicon), in a scratch directory. Output that comes from a run is shown as printed, with two exceptions that are labelled: the scratch directory the archive was unpacked into is shown as /opt/gradle, and other scratch paths are shortened to <scratch>. Gradle 8.14.x was not downloaded, so what is said about it comes from the official release notes and is labelled "not run here"; so are the package managers (SDKMAN!, Homebrew, MacPorts, Chocolatey, Scoop, winget), which were not installed or run, and Windows. The commands use a POSIX shell; the Windows equivalents come from the official page. The build files that gradle init generates are shown in the Kotlin DSL first and in the Groovy DSL where they differ.

A Gradle installation is a directory: a launcher script, about two hundred JARs and an empty init.d directory. Nothing else is installed on the machine. Everything Gradle needs while it works (the dependencies and plugins of a build, the distributions the wrapper downloads, the daemon logs) goes into the Gradle User Home, which is created on first use and is the subject of Directories & Logging. The most important sentence of the whole installation story is the first one in the official page: you do not need to install Gradle at all to run an existing project that carries the wrapper, and the usual reason to install it is to run gradle init once, which creates the wrapper. The model that explains what the installed program then does is in Core Concepts; the choice of release is in Versions, Compatibility & Release History. The running scenario (tidepool) is described on the section landing page; for the other two tools see Ant: Installation and Maven: Installation.

Do you need to install Gradle?

The official page answers it with one test: the Gradle Wrapper is identifiable by the presence of the gradlew and gradlew.bat files in the root of the project. If they are there, you do not install Gradle; you only need a suitable JDK, and you run ./gradlew (or gradlew.bat on Windows). The wrapper downloads the Gradle version the project asks for into the Gradle User Home the first time and uses it from then on, so every developer and every CI job runs the same version. That mechanism, the files it consists of and how to upgrade it are in Gradle Wrapper. Two more cases need no installation: Android Studio ships with a working Gradle, and the IDEs that import Gradle builds drive Gradle through the Tooling API (see CI & IDE Integration).

You need an installed Gradle when you start a project from scratch on the command line, because gradle init is what creates the wrapper files, and for the occasional task that is not tied to a project (running gradle --version, trying a release candidate, or gradle wrapper in a project that has none). After that the installed program is a convenience: with a project that has a wrapper, ./gradlew is the command to use, and gradle runs whatever version happens to be first on the PATH, which can be the wrong one.

Prerequisites

Gradle needs a Java Development Kit, version 17 or higher, to run. The compatibility page states the range as a JVM between 17 and 27 for 9.8.1, with 28 and later "not yet supported", and it adds that the wrapper, the Gradle client, the Tooling API client and the TestKit client are compatible with JVM 8. The 17 floor is the change of Gradle 9.0.0: the release notes say the JVM is required "to start the Gradle daemon", and that building with older JVMs is done by naming a separate JDK in a toolchain. Gradle 8.14.x still ran on Java 8 (not run here; the compatibility table lists 8 as supported for running Gradle through 8.14.x).

Gradle does not need Kotlin, Groovy or Ant installed: it ships its own, and an existing installation of them is ignored. The gradle --version output below shows the embedded versions for 9.8.1: Kotlin 2.4.10, Groovy 4.0.33 and Ant 1.10.17.

The Java that runs Gradle and the Java that compiles your project are two different settings. The first is the one found by the gradle script (below); the second is chosen by the project, normally with a toolchain, which is the reason a build can compile for Java 8 on a Gradle that itself needs 17. See Toolchains and the way the daemon picks its own JVM in Gradle Daemon. To check what you have:

java -version
java version "26" 2026-03-17
Java(TM) SE Runtime Environment (build 26+35-2893)
Java HotSpot(TM) 64-Bit Server VM (build 26+35-2893, mixed mode, sharing)

The official page lists three places Gradle looks for a JDK: the PATH, the JDK an IDE uses, and the one the project specifies. For the command line it is the JAVA_HOME environment variable if set, otherwise the java on the PATH; the script’s logic is shown in What the launcher script does. Running a JDK older than 17 (a JDK 11, for example) was not run here, because no such JDK is installed; no output is invented for it.

Getting Gradle

The distributions

Every release is published as two ZIP files. The binary-only distribution (-bin) contains the program; the complete one (-all) adds the documentation and the sources. The official recommendation is the -bin file: it is smaller and quick to download, and the documentation is online. For 9.8.1 the -bin archive is 151,620,101 bytes (about 145 MB) and unpacks to 165 MB. The same choice, -bin or -all, returns for the wrapper, which downloads one of these two files; why -bin is the better default is in Gradle Wrapper.

Gradle publishes the list of releases on https://gradle.org/releases/ and the same information in a small JSON document that tools use. The "current" endpoint is a handy way to ask which release is current, where its archive is, and which SHA-256 it should have; it is metadata, not a download of the distribution:

curl -s https://services.gradle.org/versions/current
{
  "version" : "9.8.1",
  "buildTime" : "20261007165746+0000",
  "commitId" : "39daf39feb60a18b1b6ddccde53366522fef516f",
  "current" : true,
  "snapshot" : false,
  "nightly" : false,
  "releaseNightly" : false,
  "activeRc" : false,
  "rcFor" : "",
  "milestoneFor" : "",
  "broken" : false,
  "downloadUrl" : "https://services.gradle.org/distributions/gradle-9.8.1-bin.zip",
  "checksumUrl" : "https://services.gradle.org/distributions/gradle-9.8.1-bin.zip.sha256",
  "checksum" : "dce76f55f8e251a3a1f130eb120f30b3d271de2b76c9b0729d316b5a1b6dc01f",
  "wrapperChecksumUrl" : "https://services.gradle.org/distributions/gradle-9.8.1-wrapper.jar.sha256",
  "wrapperChecksum" : "3b8a25775a69158b5ad2b1d17a80a88dc7b40a352a73eb27d06c612a7ce68e98",
  "releasedOrActiveRc" : true,
  "publicationSlot" : "9.8.1",
  ...

The listing is cut after publicationSlot (an ellipsis line marks the cut). The fields activeRc, snapshot and nightly exist because the same family of endpoints also describes release candidates and nightly builds.

Verifying the download

Gradle publishes a SHA-256 checksum for every distribution and for every gradle-wrapper.jar on its release-checksums page, and next to the file itself (a .sha256 file that services.gradle.org answers with a redirect to downloads.gradle.org). A checksum protects you from a damaged or substituted download only as far as you trust the channel the checksum came from: fetch it from gradle.org over HTTPS, not from the place you got the ZIP. Gradle also publishes a detached PGP signature (gradle-9.8.1-bin.zip.asc is served next to the archive, and the manual says Gradle publishes PGP signatures for all distribution artifacts), as Apache does for Ant and Maven; checking it needs Gradle’s public key, and that check was not run here. The checksum file holds the bare hexadecimal value and no file name, so the shasum -c form needs the name added to the line. The first check below is the real archive; the second appends one byte to a copy of it:

curl -sL https://services.gradle.org/distributions/gradle-9.8.1-bin.zip.sha256 -o gradle-9.8.1-bin.zip.sha256
cat gradle-9.8.1-bin.zip.sha256
echo "$(cat gradle-9.8.1-bin.zip.sha256)  gradle-9.8.1-bin.zip" | shasum -a 256 -c -
cp gradle-9.8.1-bin.zip tampered.zip && printf 'x' >> tampered.zip
echo "$(cat gradle-9.8.1-bin.zip.sha256)  tampered.zip" | shasum -a 256 -c -
echo "exit status of the second check: $?"
dce76f55f8e251a3a1f130eb120f30b3d271de2b76c9b0729d316b5a1b6dc01f
gradle-9.8.1-bin.zip: OK
tampered.zip: FAILED
shasum: WARNING: 1 computed checksum did NOT match
exit status of the second check: 1

The value is the one in the JSON above and the one on the release-checksums page; it is also the distributionSha256Sum that a wrapper for 9.8.1 pins. On Linux use sha256sum -c in place of shasum -a 256 -c; on Windows certutil -hashfile gradle-9.8.1-bin.zip SHA256 prints the digest to compare by eye (not run here). Checking the wrapper JAR, validating the wrapper on CI and the other supply-chain controls are in Supply-Chain Security; the wrapper properties that pin the checksum are in Gradle Wrapper.

Installing manually

The manual installation has the same three steps on every system: download, unpack, put the bin directory on the PATH.

Unpacking and the directory layout

The official examples unpack into /opt/gradle on Linux and /usr/local/gradle on macOS. Any directory works, and a directory per version (gradle-9.8.1) lets several versions live side by side. The run below unpacked the verified archive into a scratch directory that is shown as /opt/gradle:

mkdir -p /opt/gradle
unzip -q gradle-9.8.1-bin.zip -d /opt/gradle
cd /opt/gradle/gradle-9.8.1
ls
ls bin
ls lib | wc -l
ls lib/plugins | wc -l
ls -d lib/*/
du -sh .
LICENSE
NOTICE
README
bin
init.d
lib

gradle
gradle.bat

     430
     279
lib/agents/
lib/api/
lib/plugins/
165M

The layout is small:

Entry Purpose

bin/gradle, bin/gradle.bat

The launcher scripts, for POSIX shells and for Windows. The script is generated by Gradle’s own start-script generator, the same one behind the application plugin’s startScripts task.

lib/

The core JARs (209 of them, with a .properties file beside each), lib/plugins/ with the 279 files of the core plugins, lib/api/ and lib/agents/ (the instrumentation agent JAR that the script passes to the client JVM with -javaagent).

init.d/

Initialization scripts run for every build of every user of this installation. It contains one file, readme.txt, which says that .gradle scripts added here run at the start of the build. See Files in the installation directory.

LICENSE, NOTICE, README

The Apache 2.0 licence and the notices. The README of the -bin distribution points at the online manual and suggests gradle help and gradle tasks.

There is no conf directory and no global settings.xml-style file as in Maven: the configuration lives in the Gradle User Home and in the projects, and the installation only contributes the two optional files described in Files in the installation directory.

The PATH and GRADLE_HOME

Put the bin directory on the PATH:

export PATH=$PATH:/opt/gradle/gradle-9.8.1/bin

The official page offers an alternative: define GRADLE_HOME and put $GRADLE_HOME/bin on the PATH, so that upgrading means changing one variable.

export GRADLE_HOME=/opt/gradle/gradle-9.8.1
export PATH=${GRADLE_HOME}/bin:${PATH}

Put the lines in the shell start-up file (.bash_profile, .zshrc, /etc/profile.d/) so that every new shell has them. On Windows the official steps are the System Properties dialog (Advanced System Settings, Environment Variables, edit Path, add C:\Gradle\gradle-9.8.1\bin) or the same GRADLE_HOME and %GRADLE_HOME%\bin pair (not run here).

GRADLE_HOME is only a convenience for building the PATH: neither launcher script reads it. The POSIX script finds its own directory from the path it was started with, following symbolic links, and the Windows batch file does the same (a search for GRADLE_HOME in bin/gradle.bat finds nothing; the batch file was not run). The run below sets GRADLE_HOME to a directory that does not exist and still gets the version, and starts the script through a symbolic link in another directory:

GRADLE_HOME=/nonexistent gradle --version | sed -n 3p
mkdir -p ln && ln -sf /opt/gradle/gradle-9.8.1/bin/gradle ln/gradle
ln/gradle --version | sed -n 3p
Gradle 9.8.1
Gradle 9.8.1

This is why advice that tells you to set GRADLE_HOME as a requirement is out of date, and why the PATH entry (or a symbolic link such as /usr/local/bin/gradle) is all that matters. The GRADLE_HOME that Gradle documents as a configuration location is the installation directory itself; see Files in the installation directory.

What the launcher script does

The 248-line bin/gradle script (wc -l on the 9.8.1 distribution) is a small POSIX program; reading it explains most installation problems. In order, it resolves its own directory (APP_HOME, through symbolic links), then picks the Java: $JAVA_HOME/bin/java when JAVA_HOME is set (and fails with an error if it is not a directory with a java in it), otherwise the java found on the PATH (and fails if there is none). It then builds the command line of the client JVM from three sources, in this order: the script’s defaults, JAVA_OPTS and GRADLE_OPTS. The defaults are -Xmx64m -Xms64m and the instrumentation agent, which is a lightweight client: the real work happens in the daemon, a separate JVM that the client starts or reuses, whose options are not these. The last line runs java -jar lib/gradle-gradle-cli-main-9.8.1.jar with the arguments you gave.

The consequence people trip over: JAVA_OPTS and GRADLE_OPTS set the options of the small client JVM; the daemon’s heap is org.gradle.jvmargs in gradle.properties (default -Xmx512m; see Build Environment & Properties), and which JVM the daemon uses is the subject of Gradle Daemon. The environment variables Gradle reads are listed in the build-environment page of the manual.

Package managers

The official page lists these, and warns that the version a package manager distributes "is not controlled by Gradle, Inc." For Linux it adds that the distribution packages "may distribute a modified version of Gradle that is incompatible or incomplete". None of the commands below was run here.

System Command Notes

SDKMAN! (Linux, macOS, Windows through WSL)

sdk install gradle

The one manager the official page says Gradle itself deploys to ("Gradle is deployed and maintained by SDKMAN!"). A specific version is sdk install gradle 9.8.1, sdk list gradle shows the candidates and sdk use or sdk default select one.

Homebrew (macOS, Linux)

brew install gradle

The formula tracks the current release (the Homebrew API listed 9.8.1 on 2026-10-16) and depends on openjdk and gradle-completion, so it can install a JDK you did not ask for.

MacPorts (macOS)

sudo port install gradle

Listed on the official page.

Chocolatey (Windows)

choco install gradle

Scoop (Windows)

scoop install gradle

winget (Windows)

winget install Gradle.Gradle

SDKMAN! is the choice when you switch between Gradle versions on one machine, and it manages JDKs the same way (sdk install java); a Homebrew or distribution package is convenient but may lag or change what is shipped. In every case the project’s wrapper is what pins the version for the team, so the installed version matters only for gradle init and for projects without a wrapper.

Source: SDKMAN!: Usage

Verifying the installation

Open a new shell and run gradle -v (the same as gradle --version):

gradle --version
------------------------------------------------------------
Gradle 9.8.1
------------------------------------------------------------

Build time:    2026-10-07 16:57:46 UTC
Revision:      39daf39feb60a18b1b6ddccde53366522fef516f

Kotlin:        2.4.10
Groovy:        4.0.33
Ant:           Apache Ant(TM) version 1.10.17 compiled on April 6 2026
Launcher JVM:  26 (Oracle Corporation 26+35-2893)
Daemon JVM:    /Library/Java/JavaVirtualMachines/jdk-26.jdk/Contents/Home (no Daemon JVM specified, using current Java home)
OS:            Mac OS X 27.0.1 aarch64

What each line tells you:

  • Build time and Revision identify the exact build; the revision is the Git commit and matches the commitId of the JSON above.

  • Kotlin, Groovy and Ant are the versions embedded in this Gradle: the language versions your build scripts are compiled with and the Ant that ant.importBuild and the Ant builder use. Gradle 9.8.1 embeds Kotlin 2.4.10 and Groovy 4.0.33; Gradle 9.0.0 moved to Kotlin 2.2 and Groovy 4, 8.14.x embeds Kotlin 2.0 with language version 1.8 and Groovy 3 (not run here; the 9.0.0 release notes state the change).

  • Launcher JVM is the JVM that ran the client, chosen by JAVA_HOME or the PATH.

  • Daemon JVM is the JVM the daemon would use. Outside a project that declares a daemon toolchain it is the same Java home, and the output says so ("no Daemon JVM specified, using current Java home"). In a project with gradle/gradle-daemon-jvm.properties the line shows the criteria from that file instead; creating the file is covered with the daemon.

  • OS is the platform, which matters when a build downloads platform-specific binaries.

JAVA_HOME changes the first two JVM lines. The run below points it at a JetBrains Runtime 17, the oldest JDK Gradle 9.8.1 accepts (the home directory is shown as <home>):

JAVA_HOME=<home>/Library/Java/JavaVirtualMachines/jbr-17.0.14/Contents/Home gradle --version
...
Launcher JVM:  17.0.14 (JetBrains s.r.o. 17.0.14+1-b1367.22)
Daemon JVM:    <home>/Library/Java/JavaVirtualMachines/jbr-17.0.14/Contents/Home (no Daemon JVM specified, using current Java home)
OS:            Mac OS X 27.0.1 aarch64

The first lines are the same as above and are cut (an ellipsis line marks the cut). gradle --version does not need a project, and it does not start a daemon: gradle --status run right after it, with a fresh Gradle User Home, reported that no Gradle daemons were running.

Files in the installation directory

Two things placed inside the installation affect every build that uses it, for every user of the machine. Both are rarely right (a project should not depend on how a machine was set up), but they explain surprising behaviour on shared build servers and they are the answer to "what is that init.d directory".

gradle.properties. The build-environment page lists the installation directory (GRADLE_HOME) as the lowest-precedence location of gradle.properties, below the Gradle User Home and the project. The probe below reads a property named tidepool.origin through a provider. The build file is the same project in both DSLs.

Kotlin DSL
// build.gradle.kts
val tidepoolOrigin = providers.gradleProperty("tidepool.origin").orElse("not set")

tasks.register("showOrigin") {
    val origin = tidepoolOrigin
    doLast { println("tidepool.origin = ${origin.get()}") }
}
Groovy DSL
// build.gradle
def tidepoolOrigin = providers.gradleProperty('tidepool.origin').orElse('not set')

tasks.register('showOrigin') {
    def origin = tidepoolOrigin
    doLast { println "tidepool.origin = ${origin.get()}" }
}

Both projects have a one-line settings file (rootProject.name = "probe" or rootProject.name = 'probe'). They were run with --configuration-cache, and the example is compatible with it: the value is captured as a provider in a local variable, never through project inside the task action. Three runs per DSL, with a gradle.properties file added to and removed from the installation directory between them (Kotlin DSL shown; the Groovy DSL project printed the same three lines):

gradle showOrigin -q --configuration-cache
echo "tidepool.origin=installation" > /opt/gradle/gradle-9.8.1/gradle.properties
gradle showOrigin -q --configuration-cache
echo "tidepool.origin=project" > gradle.properties      # in the project directory
gradle showOrigin -q
tidepool.origin = not set
tidepool.origin = installation
tidepool.origin = project

The project’s value wins over the installation’s, as the documented precedence says. The Groovy DSL project printed installation with the file present and not set after it was removed. (The file was removed after the experiment.)

init.d. Every .gradle file in init.d is an initialization script and runs at the start of each build; the folder of the Gradle User Home (~/.gradle/init.d) works the same way for one user. Initialization scripts can be written in either language; a Kotlin one is named *.init.gradle.kts. A one-line script in each language, placed in the installation’s init.d for one run:

Kotlin DSL
// init.d/hello.init.gradle.kts
gradle.rootProject {
    println("Kotlin init script from the installation ran for $name")
}
Groovy DSL
// init.d/hello.gradle
gradle.rootProject {
    println "init.d script from the installation ran for ${it.name}"
}
Kotlin init script from the installation ran for probe
init.d script from the installation ran for probe

(The two scripts were run separately; each printed its own line once before the task output.) Init scripts, their use for organisation-wide repositories and plugins, and the Tooling API are in Init Scripts & the Tooling API.

Bootstrapping a project with gradle init

Once Gradle is installed, gradle init creates a new build. The official description of the generated files: a build script, a settings file, the Gradle Wrapper files and, for the non-basic project types, sample source code. The init task belongs to the Build Init plugin, which is applied automatically to the root project of every build, so no build file is needed to run it. Its options are listed by Gradle itself:

mkdir h && cd h
gradle help --task init
> Task :help
Detailed task information for init

Path
     :init (registered by plugin 'org.gradle.build-init')

Type
     InitBuild (org.gradle.buildinit.tasks.InitBuild)

Options
     --comments     Include clarifying comments in files.

     --no-comments     Disables option --comments.

     --dsl     Set the build script DSL to be used in generated scripts.
               Available values are:
                    groovy
                    kotlin

     --incubating     Allow the generated build to use new features and APIs.

     --no-incubating     Disables option --incubating.

     --insecure-protocol     How to handle insecure URLs used for Maven Repositories.
                             Available values are:
                                  ALLOW
                                  FAIL
                                  UPGRADE
                                  WARN

     --into     Set the directory where the project is generated.

     --java-version     Provides java version to use in the project.

     --overwrite     Allow existing files in the build directory to be overwritten?

     --no-overwrite     Disables option --overwrite.

     --package     Set the package for source files.

     --project-name     Set the project name.

     --split-project     Split functionality across multiple subprojects?

     --no-split-project     Disables option --split-project.

     --test-framework     Set the test framework to be used.
                          Available values are:
                               cpptest
                               junit
                               junit-jupiter
                               kotlintest
                               scalatest
                               spock
                               testng
                               xctest

     --type     Set the type of project to generate.
                Available values are:
                     basic
                     cpp-application
                     cpp-library
                     groovy-application
                     groovy-gradle-plugin
                     groovy-library
                     java-application
                     java-gradle-plugin
                     java-library
                     kotlin-application
                     kotlin-gradle-plugin
                     kotlin-library
                     pom
                     scala-application
                     scala-library
                     swift-application
                     swift-library

     --use-defaults     Use default values for options not configured explicitly

     --no-use-defaults     Disables option --use-defaults.

     --rerun     Causes the task to be re-run even if up-to-date.

Description
     Initializes a new Gradle build.

Group
     Build Setup

BUILD SUCCESSFUL in 295ms
1 actionable task: 1 executed

The options, with the behaviour the manual documents:

Option Meaning

--type

The kind of build. Without it Gradle infers pom when it finds a pom.xml (a Maven conversion, see below) and uses basic otherwise. The types besides basic and pom are an application, a library and, for Java, Kotlin and Groovy, a Gradle plugin.

--dsl

kotlin or groovy. The default is the Kotlin DSL for most types and the Groovy DSL for the Groovy types. Kotlin became the default of gradle init in Gradle 8.2.

--test-framework

junit-jupiter is the default for Java; junit (JUnit 4), spock and testng are alternatives, and scalatest, kotlintest, cpptest and xctest belong to the other languages.

--project-name, --package

The project name (default: the name of the directory) and the package of the generated sources (default: the project name).

--java-version

The Java version of the generated toolchain (see the toolchain block in the output below). For the Java types it is asked for interactively; with --use-defaults it is 21 (see below).

--split-project / --no-split-project

Whether the sample is spread over several subprojects with shared convention plugins, or is a single application project.

--into

The directory to generate into. It is created if it does not exist. New in Gradle 9.5.0; before that you created the directory and ran gradle init in it (not run here; stated by the 9.5.0 release notes).

--use-defaults

Take the default for every option not given, without prompting.

--incubating, --comments, --overwrite, --insecure-protocol

Allow incubating APIs in the generated scripts, include or drop the explanatory comments, overwrite existing files, and (for the pom conversion) how to treat Maven repositories on plain http URLs: fail, allow, warn (the default) or upgrade.

Interactive and non-interactive runs

gradle init with no options is meant for a terminal: Gradle lists the project types and asks about the DSL, the test framework and the names. When there is no interactive console, Gradle does not ask; it uses what it can infer. Running it in an empty directory with its input piped from a script and --console=plain, which is how a CI job or a script would run it, shows what that gives:

mkdir interactive && cd interactive
printf '2\n1\n1\n1\nio.tidepool\n\n1\n' | gradle init --console=plain
find . -type f | sort
> Task :init
Learn more about Gradle by exploring our Samples at https://docs.gradle.org/9.8.1/samples

BUILD SUCCESSFUL in 294ms
1 actionable task: 1 executed
./.gitattributes
./.gitignore
./build.gradle.kts
./gradle.properties
./gradle/libs.versions.toml
./gradle/wrapper/gradle-wrapper.jar
./gradle/wrapper/gradle-wrapper.properties
./gradlew
./gradlew.bat
./settings.gradle.kts

The piped answers were ignored (no questions were printed) and the result is the basic type with the Kotlin DSL: an empty build script, a settings file naming the project after the directory, the wrapper, an empty version catalog and a gradle.properties whose only content is org.gradle.configuration-cache=true. So a scripted gradle init must pass its choices as options; the interactive questions were not exercised here, because they need a terminal.

Two details of the generated files are worth knowing. The wrapper properties come out pointing at the official distribution:

distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.8.1-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

There is no distributionSha256Sum: init does not pin the checksum, so adding it (gradle wrapper --gradle-distribution-sha256-sum=…​) is the first hardening step; see Gradle Wrapper. And gradle.properties already turns the configuration cache on, the preferred mode since Gradle 9.0, so projects bootstrapped by 9.8.1 start with it enabled (it is still off by default for projects that do not set it; Gradle 10 is announced to turn it on).

Bootstrapping tidepool: Kotlin DSL

The running scenario is a multi-module Java build (a model, an engine and a command-line interface). gradle init cannot produce that exact layout, but the Java application type with --split-project produces the same shape (an application that depends on libraries) and is the way the official tutorial starts. Non-interactive, with every choice given:

gradle init --type java-application --dsl kotlin --test-framework junit-jupiter \
  --package io.tidepool --project-name tidepool --split-project --java-version 26 --into tidepool
find tidepool -type f -not -path '*/.gradle/*' | sort
> Task :init
Learn more about Gradle by exploring our Samples at https://docs.gradle.org/9.8.1/samples/sample_building_java_applications_multi_project.html

BUILD SUCCESSFUL in 275ms
1 actionable task: 1 executed
tidepool/.gitattributes
tidepool/.gitignore
tidepool/app/build.gradle.kts
tidepool/app/src/main/java/io/tidepool/app/App.java
tidepool/app/src/main/java/io/tidepool/app/MessageUtils.java
tidepool/app/src/test/java/io/tidepool/app/MessageUtilsTest.java
tidepool/buildSrc/build.gradle.kts
tidepool/buildSrc/settings.gradle.kts
tidepool/buildSrc/src/main/kotlin/buildlogic.java-application-conventions.gradle.kts
tidepool/buildSrc/src/main/kotlin/buildlogic.java-common-conventions.gradle.kts
tidepool/buildSrc/src/main/kotlin/buildlogic.java-library-conventions.gradle.kts
tidepool/gradle.properties
tidepool/gradle/libs.versions.toml
tidepool/gradle/wrapper/gradle-wrapper.jar
tidepool/gradle/wrapper/gradle-wrapper.properties
tidepool/gradlew
tidepool/gradlew.bat
tidepool/list/build.gradle.kts
tidepool/list/src/main/java/io/tidepool/list/LinkedList.java
tidepool/list/src/test/java/io/tidepool/list/LinkedListTest.java
tidepool/settings.gradle.kts
tidepool/utilities/build.gradle.kts
tidepool/utilities/src/main/java/io/tidepool/utilities/JoinUtils.java
tidepool/utilities/src/main/java/io/tidepool/utilities/SplitUtils.java
tidepool/utilities/src/main/java/io/tidepool/utilities/StringUtils.java

What was generated, with the explanatory comments left out for brevity:

// settings.gradle.kts
plugins {
    // Apply the foojay-resolver plugin to allow automatic download of JDKs
    id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
}

rootProject.name = "tidepool"
include("app", "list", "utilities")
// app/build.gradle.kts
plugins {
    id("buildlogic.java-application-conventions")
}

dependencies {
    implementation("org.apache.commons:commons-text")
    implementation(project(":utilities"))
}

application {
    mainClass = "io.tidepool.app.App"
}
// buildSrc/src/main/kotlin/buildlogic.java-common-conventions.gradle.kts
plugins {
    java
}

repositories {
    mavenCentral()
}

dependencies {
    constraints {
        implementation("org.apache.commons:commons-text:1.14.0")
    }

    testImplementation("org.junit.jupiter:junit-jupiter:6.0.1")
    testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(26)
    }
}

tasks.named<Test>("test") {
    useJUnitPlatform()
}

Several things here differ from the conventions this section recommends, and are worth noticing because gradle init is where most people meet Gradle first. The shared build logic lives in buildSrc, the older of the two ways to share it; the recommended place is an included build named build-logic (see Convention Plugins & Sharing Build Logic). Repositories are declared in the convention plugin, not once in the settings file, and the dependency versions are in the convention plugin (a constraint, and the JUnit coordinate) while the version catalog is empty. The toolchain is the --java-version you gave (26 here), and the settings file applies the Foojay resolver so that a missing JDK can be downloaded for the toolchain. The versions in the templates (JUnit 6.0.1, commons-text 1.14.0, the Foojay plugin 1.0.0) are fixed inside Gradle 9.8.1 and are not refreshed at generation time, so they may be behind the latest releases: update them after generating. The next pages of the section rework this layout into the conventions used for tidepool.

The generated project builds with the installed Gradle (the first run downloaded JUnit and commons-text from Maven Central). It was built with --configuration-cache, and is compatible with it: the first build stored an entry and the later one reused it.

cd tidepool
gradle build --configuration-cache
gradle run --quiet
gradle build --configuration-cache
...
> Task :list:build
> Task :app:test
> Task :app:check
> Task :app:build

BUILD SUCCESSFUL in 7s
22 actionable tasks: 22 executed
Configuration cache entry stored.
Hello World!
> Task :app:assemble UP-TO-DATE
> Task :app:test UP-TO-DATE
> Task :app:check UP-TO-DATE
> Task :app:build UP-TO-DATE

BUILD SUCCESSFUL in 507ms
13 actionable tasks: 13 up-to-date
Configuration cache entry reused.

Only the last lines of the first build are shown (the ellipsis line marks the cut), then the output of gradle run --quiet, then the last lines of the repeat build. The first build executed 22 tasks (a first-ever run is slower, because it also downloads the dependencies and the build-logic plugins), the repeat build found everything up to date and reused the cache entry, and run printed Hello World!. Running the generated ./gradlew instead would use the wrapper properties above, which download the 9.8.1 distribution from services.gradle.org; it was not run here, because the installed 9.8.1 is the same version and no second download was made.

Bootstrapping tidepool: Groovy DSL

Only --dsl groovy changes; everything else is the same command:

gradle init --type java-application --dsl groovy --test-framework junit-jupiter \
  --package io.tidepool --project-name tidepool --split-project --java-version 26 --into tidepool
find tidepool -name '*.gradle' | sort
tidepool/app/build.gradle
tidepool/buildSrc/build.gradle
tidepool/buildSrc/settings.gradle
tidepool/buildSrc/src/main/groovy/buildlogic.java-application-conventions.gradle
tidepool/buildSrc/src/main/groovy/buildlogic.java-common-conventions.gradle
tidepool/buildSrc/src/main/groovy/buildlogic.java-library-conventions.gradle
tidepool/list/build.gradle
tidepool/settings.gradle
tidepool/utilities/build.gradle

The Groovy convention plugins live in src/main/groovy instead of src/main/kotlin. The same files in Groovy syntax (single quotes and no parentheses for method calls are the visible differences):

// settings.gradle
plugins {
    // Apply the foojay-resolver plugin to allow automatic download of JDKs
    id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0'
}

rootProject.name = 'tidepool'
include('app', 'list', 'utilities')
// app/build.gradle
plugins {
    id 'buildlogic.java-application-conventions'
}

dependencies {
    implementation 'org.apache.commons:commons-text'
    implementation project(':utilities')
}

application {
    mainClass = 'io.tidepool.app.App'
}
// buildSrc/src/main/groovy/buildlogic.java-common-conventions.gradle
plugins {
    id 'java'
}

repositories {
    mavenCentral()
}

dependencies {
    constraints {
        implementation 'org.apache.commons:commons-text:1.14.0'
    }

    testImplementation 'org.junit.jupiter:junit-jupiter:6.0.1'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(26)
    }
}

tasks.named('test') {
    useJUnitPlatform()
}

This project was built and run too, with --configuration-cache and the installed Gradle: the build succeeded (20 actionable tasks executed), run printed Hello World! and the repeat build reported 13 up-to-date tasks and Configuration cache entry reused. The differences between the DSLs are the subject of Kotlin DSL and Groovy DSL & Migrating to Kotlin.

Without --split-project, and with --use-defaults

Without --split-project the sample is one subproject. Even then the build has a subproject, named app, and a root project with no code (Kotlin DSL; the Groovy output has the same shape):

gradle init --type java-application --dsl kotlin --test-framework junit-jupiter \
  --package io.tidepool --project-name tidepool --no-split-project --java-version 26 --into tidepool
find tidepool -type f -not -path '*/.gradle/*' -not -name 'gradlew*' | sort
cat tidepool/gradle/libs.versions.toml
tidepool/.gitattributes
tidepool/.gitignore
tidepool/app/build.gradle.kts
tidepool/app/src/main/java/io/tidepool/App.java
tidepool/app/src/test/java/io/tidepool/AppTest.java
tidepool/gradle.properties
tidepool/gradle/libs.versions.toml
tidepool/gradle/wrapper/gradle-wrapper.jar
tidepool/gradle/wrapper/gradle-wrapper.properties
tidepool/settings.gradle.kts
# This file was generated by the Gradle 'init' task.
# https://docs.gradle.org/current/userguide/version_catalogs.html#sec::toml-dependencies-format

[versions]
guava = "33.5.0-jre"
junit-jupiter = "6.0.1"

[libraries]
guava = { module = "com.google.guava:guava", version.ref = "guava" }
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" }

Here the generated build uses a version catalog (libs.guava, libs.junit.jupiter), the layout the section recommends, and declares the repository in the subproject. (The catalog is shown as plain text because the documentation viewer has no TOML highlighting.) Version catalogs are covered in Version Catalogs.

--use-defaults fills every option not given with its default and does not prompt. With only a type and a directory:

gradle init --use-defaults --type java-library --into lib
grep -rn "languageVersion" lib --include='*.kts'
lib/lib/build.gradle.kts:34:        languageVersion = JavaLanguageVersion.of(21)

The default Java version of the generated toolchain is 21 in Gradle 9.8.1, not the JDK that runs Gradle (26 here), so the build will need JDK 21 or let the Foojay resolver download one. Pass --java-version to choose.

Failure modes

gradle init refuses to write over a directory that already has files, and rejects unknown types. Both messages are as printed (the scratch path is shortened to <scratch>):

gradle init --type java-application --dsl groovy --test-framework junit-jupiter \
  --package io.tidepool --project-name tidepool --split-project --java-version 26 --into tidepool
> Task :init FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':init' (registered by plugin 'org.gradle.build-init').
> Aborting build initialization due to existing files in the project directory: '<scratch>/tidepool'.

* Try:
> Remove any existing files in the project directory and run the init task again.
> Enable the --overwrite option to allow existing files to be overwritten.
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights from a Build Scan (powered by Develocity).
> Get more help at https://help.gradle.org.

BUILD FAILED in 261ms
gradle init --type nonsense --into x
> Task :init FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':init' (registered by plugin 'org.gradle.build-init').
> The requested build type 'nonsense' is not supported. Supported types:
    - 'basic'
    - 'cpp-application'
    - 'cpp-library'
    - 'groovy-application'
    - 'groovy-gradle-plugin'
    - 'groovy-library'
    - 'java-application'
    - 'java-gradle-plugin'
    - 'java-library'
    - 'kotlin-application'
    - 'kotlin-gradle-plugin'
    - 'kotlin-library'
    - 'pom'
    - 'scala-application'
    - 'scala-library'
    - 'swift-application'
    - 'swift-library'

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights from a Build Scan (powered by Develocity).
> Get more help at https://help.gradle.org.

BUILD FAILED in 600ms

--overwrite replaces existing files, which is the reason to run init only in an empty directory or in a Git working tree where you can see what changed.

Converting a Maven build

In a directory with a pom.xml, gradle init infers the pom type and converts the effective POM (so inheritance, dependency management and properties are resolved) to Gradle files; single and multi-module builds are supported. The manual states the limits: Maven and Gradle differ fundamentally, so the result "should be considered a starting point for further refinement". Compile-scope dependencies become api dependencies of the java-library plugin, because Maven exposes them to consumers, and the manual encourages moving as many as possible to implementation. Plain http repository URLs are handled according to --insecure-protocol. The conversion is walked through, with tidepool’s Maven build, in Migrating from Maven; the comparison of the three tools' migration paths is in Migrating Between Tools. The conversion was not run on this page.

Troubleshooting an installation

The official troubleshooting page covers three installation problems. Each is reproduced here where it can be, with the message Gradle 9.8.1 prints.

gradle is not found. A shell without the bin directory on the PATH:

env PATH=/usr/bin:/bin sh -c 'gradle --version'
echo "exit status: $?"
sh: gradle: command not found
exit status: 127

Exit status 127 is the shell’s, not Gradle’s. The fix is the PATH entry above, in a new shell.

JAVA_HOME is set to an invalid directory. The launcher checks that $JAVA_HOME/bin/java exists:

JAVA_HOME=/nonexistent gradle --version
echo "exit status: $?"
ERROR: JAVA_HOME is set to an invalid directory: /nonexistent

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation.

exit status: 1

The fix is to point JAVA_HOME at the directory that contains bin/java (on macOS, /usr/libexec/java_home -v 17 prints the home of an installed JDK 17; here it printed the JetBrains Runtime used above), or to unset it. A frequent variant is a JAVA_HOME that points at a JDK that was upgraded or removed.

No Java at all. With neither JAVA_HOME nor a java on the PATH (the run uses a PATH that contains only a uname link, which the script calls first):

env -i PATH=$PWD/nojava /bin/sh /opt/gradle/gradle-9.8.1/bin/gradle --version
echo "exit status: $?"
ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation.

exit status: 1

Permission denied. The scripts must be executable; an installation copied with a tool that drops the mode bits fails like this (a copy of the installation with chmod -x bin/gradle):

cp -R /opt/gradle/gradle-9.8.1 perm-copy
chmod -x perm-copy/bin/gradle
bash -c './perm-copy/bin/gradle --version'
echo "exit status: $?"
bash: ./perm-copy/bin/gradle: Permission denied
exit status: 126

The message comes from the shell (sh -c printed the same line with an sh: prefix); 126 is the shell’s "found but not executable". The fix is chmod +x.

gradle --version works but every build fails. Then the installation is fine and a build script is broken. The official page suggests gradle help, which runs configuration but no tasks: if it fails the same way, the problem is in the build configuration; if not, it is in a task. A Java too old for Gradle 9 (a JDK 11, for instance) fails at start-up with an error that names the Java version; that case was not run here. The wider catalogue of build failures, the dependency-resolution problems and how to debug a build are in Best Practices & Troubleshooting.

Installing the other two tools

Apache Ant Apache Maven Gradle

Needs

Java 8 or later

Java 8 or later (Maven 4 release candidate: 17)

Java 17 or later (the wrapper and clients: 8)

What you install

A ZIP or tar of scripts and JARs

A ZIP or tar of scripts and JARs

A ZIP of scripts and JARs

Home variable

ANT_HOME, used by the launcher (it can guess it)

None; computed from the script location (M2_HOME removed in 3.5.0)

GRADLE_HOME, not read by the launcher

Verification

SHA-512 and PGP signature

SHA-512 and PGP signature

SHA-256 and PGP signature

Per-project pinning

None built in

Maven Wrapper

Gradle Wrapper (the normal way to run)

Bootstrap a project

Write build.xml by hand

mvn archetype:generate

gradle init

The pages linked in the first row of the intro give the details; the pinning mechanisms are Maven Wrapper and Gradle Wrapper, and the broader comparison is Comparing Ant, Maven & Gradle.

Where the Gradle in Action book is outdated

Gradle in Action (Manning, 2014) covers installation for Gradle 1.7 in its second chapter. Four things in that start have changed. Gradle 1.x ran on Java 5 or later; Gradle 9 needs Java 17 to run, and older JDKs are reached through toolchains. The book sets GRADLE_HOME; the launcher does not read it and the PATH entry is enough. The book’s gradle setupBuild task was incubating and was replaced by gradle init (the Build Init plugin), which now defaults to the Kotlin DSL (since 8.2) rather than Groovy, supports many project types and a Maven conversion, and writes the wrapper. And the daemon, which the book presents as opt-in with --daemon, has been on by default since Gradle 3.0. The release history is in Versions, Compatibility & Release History.