Gradle
Gradle
Gradle is one of the most widely used build tools in the JVM ecosystem. This article covers Gradle's origins, how it works, and how it differs from Maven.
1. About Gradle
Hans Dockter started it in 2007 and shipped 1.0 in June 2012. The company Gradleware later renamed to Gradle Inc. License is Apache 2.0.
Build scripts (build.gradle) were originally a Groovy DSL. Kotlin DSL (build.gradle.kts) reached 1.0 in November 2018, and adoption grew quickly once Android Studio's new project default switched to Kotlin.
When Android's build tooling adopted Gradle (Android Studio's 2013 release), Gradle became the de facto standard across Java and Kotlin.
2. Core model
A Gradle build is a directed acyclic graph (DAG) of tasks. The build task depends on compileJava, which depends on processResources, and so on. Gradle resolves dependencies and runs them in the correct order.
Tasks declare inputs and outputs. When the input hash matches, Gradle pulls the result from cache. This mechanism is the primary way Gradle reduces build time.
3. Wrapper
gradlew (Unix) and gradlew.bat (Windows), along with gradle/wrapper/gradle-wrapper.properties, are committed to the repo. The properties file pins the Gradle version, and the first run downloads that version automatically. Builds run identically without Gradle preinstalled on the system.
# Windows
./gradlew.bat build
# macOS · Linux
./gradlew build
4. Daemon
Gradle runs a background daemon process to reuse JVM startup costs and caches. Enabled by default. The first run is slow; subsequent ones are short.
5. Build Cache · Configuration Cache
- Build cache — Stores task outputs as key-value pairs. Local cache is enabled by default; remote cache (a server) is optional.
- Configuration cache — Stores the result of the configuration phase so the next invocation can skip reconfiguration. After stabilizing across releases, it has been moving closer to the default in 9.x.
6. Multi-project
The root settings.gradle(.kts) declares which modules are included:
// settings.gradle.kts
rootProject.name = "myapp"
include("api", "common", "worker")
Each module gets its own build.gradle.kts, and internal dependencies are declared as implementation(project(":common")).
7. Other paths
| Tool | First release | Place |
|---|---|---|
| Apache Ant | 2000 | XML-based. Build steps written by hand. Dependency management is separate (Ivy). |
| Apache Maven | 2004 | XML (pom.xml). Convention-first with built-in dependency management. Still dominant in enterprise. |
| Gradle | 2012 (1.0) | DSL-based. Strong on incremental builds, caching, and daemon-driven build-time wins. |
| Bazel | 2015 (OSS) | OSS form of Google Blaze. Deterministic builds, remote execution. Far broader than JVM. |
Compared with Maven:
- Expression — XML tree vs DSL (code). DSL is flexible with conditionals and loops; XML is widely seen as plainer to read.
- Cache and incremental builds — Gradle treats input/output-based caching as first-class. Maven is weaker at automatically skipping unchanged modules in multi-module builds.
- Daemon — Only Gradle runs a daemon by default, cutting JVM startup overhead.
- Learning curve — Maven's strong conventions are formal, while Gradle's flexibility makes badly written builds harder to debug.
8. Common shapes
Basic commands:
./gradlew tasks # list all tasks
./gradlew :api:build # only that module
./gradlew test --tests "com.example.UserServiceTest"
./gradlew clean build --no-daemon
./gradlew dependencies # dependency tree
./gradlew --refresh-dependencies # ignore cache and re-resolve
build.gradle.kts (Spring Boot, abbreviated):
plugins {
java
id("org.springframework.boot") version "3.3.0"
id("io.spring.dependency-management") version "1.1.6"
}
group = "com.example"
version = "0.0.1-SNAPSHOT"
java { toolchain { languageVersion = JavaLanguageVersion.of(21) } }
repositories { mavenCentral() }
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
tasks.test { useJUnitPlatform() }
Frequent options in gradle.properties:
org.gradle.jvmargs=-Xmx2g
org.gradle.parallel=true
org.gradle.caching=true
org.gradle.configuration-cache=true
9. Common pitfalls
Compatibility matrix — version compatibility across Gradle, plugins, and JDKs is tight. Check the official compatibility table first when builds break.
Wrapper partially committed — won't run on other machines. Commit all four files together: gradlew, gradlew.bat, and gradle/wrapper/*.
Imperative code vs configuration cache — heavy imperative code in build.gradle(.kts) clashes with the configuration cache. Stick to the lazy APIs the official docs recommend (tasks.register, Provider).
Daemon OOM — set a sensible heap with org.gradle.jvmargs, or run CI with --no-daemon to disable the daemon entirely.
Mixed Groovy and Kotlin DSL — combining them across modules makes code sharing painful. Pick one DSL per repo.
Closing thoughts
Gradle's real value lies in incremental builds, build cache, and the daemon working together. In large multi-module setups, build times shorten visibly compared to Maven. Commit all four wrapper files for identical builds across machines. Following the lazy APIs of the configuration cache pays off long-term.
Next
- editor-setup
- linting-formatting
References include the official Gradle docs, Gradle GitHub, Gradle Kotlin DSL Primer, Compatibility Matrix, Apache Maven docs, Gradle Build Cache guide, and the Gradle blog.