Robots Atlas>ROBOTS ATLAS
Tools

Gradle โ€” what it is and how the build tool works

Sir Robot18 August 2026 ยท 12 min read
gradle-co-to-jest-i-jak-dziala-narzedzie-do-budowania

Gradle is an open-source build automation tool that compiles code, runs tests and packages applications. It is worth understanding because it is Android's official build system and a cornerstone of the JVM world โ€” anyone working with Java, Kotlin or mobile apps will run into it.

What is Gradle?

Gradle is a build automation tool โ€” not a programming language, not an application framework, and certainly not an AI model. Its role is narrow but critical: to turn a project's source code and resources into a finished product, whether that is a compiled JAR file, an Android application (APK or Android App Bundle), or a library ready to publish.

The official documentation describes Gradle as "a fast, dependable, and adaptable open-source build automation tool with an elegant and extensible declarative build language." It is primarily associated with JVM: the Java Virtual Machine โ€” the runtime that executes compiled Java, Kotlin, Scala and other code and Android development, although its plugin ecosystem allows it to orchestrate builds involving native code (C++, Swift) and other platforms as well.

An important distinction: Gradle belongs to the same category as Maven and Ant โ€” these are developer tools from the build layer, not runtime environments. Gradle does nothing once the application is live in production โ€” its job ends the moment the artifact: the finished output of a build, e.g. a JAR file or an Android app, handed off for deployment is ready to deploy.

Why do we need a build tool?

The easiest way to understand Gradle is to picture what you would do without it. Take a small Java project:

Plaintext
src/
 โ”œโ”€โ”€ UserService.java
 โ”œโ”€โ”€ UserRepository.java
 โ””โ”€โ”€ Application.java

that uses a handful of libraries:

  • Spring Boot: a framework for quickly building Java/Kotlin applications, with sensible defaults and an embedded server
  • Jackson: a Java library for serializing objects to JSON and parsing JSON
  • PostgreSQL Driver: a JDBC driver connecting a Java application to a PostgreSQL database
  • JUnit: the standard framework for writing and running unit tests in Java
  • Mockito: a Java library for creating mock objects for tests

To turn this code into a working application, someone (or something) has to run several steps in order:

Download dependencies
Compile source code
Compile tests
Run tests
Package the application
Create the JAR

Doing this by hand โ€” fetching the right library versions, invoking the compiler with the correct classpath: the list of directories and JAR archives where the JVM looks for compiled classes, keeping the order straight โ€” is tedious and error-prone. Gradle automates this whole process: you describe what should be produced, and it works out how and in what order.

Who is behind it?

Gradle was created in 2008 as an open-source project. Its creator is Hans Dockter, and it is developed by the company Gradle (Gradle, Inc.) โ€” the tool is available under the Apache License 2.0: a permissive open-source license allowing free use, modification and distribution of the code, including commercial use. Its popularity, however, was sealed by a single decision: Google's choice of it as Android's official build system. Thanks to its default integration with Android Studio, Gradle became the de facto standard for millions of mobile applications.

What does a Gradle build look like?

A build's configuration lives in a build.gradle.kts (Kotlin) or build.gradle (Groovy) script. Here is a minimal but realistic example for a Java project:

Kotlin
plugins {
    java
}

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
    testImplementation("org.junit.jupiter:junit-jupiter")
}

tasks.test {
    useJUnitPlatform()
}

The whole file reads through the lens of four blocks:

pluginswhat Gradle is able to do at all
repositorieswhere to download libraries from
dependencieswhich libraries the project needs
taskshow to configure the operations that run

The java plugin adds the full set of tasks for building a JVM project (compilation, tests, packaging). repositories points to a repository โ€” here Maven Central. dependencies declares the required libraries, grouped into configurations (buckets) that define their scope: implementation for production code, testImplementation for tests.

Tasks and the task graph (DAG)

The foundation of Gradle is the task โ€” a basic unit of work, such as compiling code or running tests. Tasks are declared in build scripts or added by plugins, and the relationships between them form a Directed Acyclic Graph (DAG). For a Java project, a simplified graph looks roughly like this:

compileJava
classes
test
run the tests
jar
package into a JAR
build

The execution order comes from the declared relationships between tasks, not from the order of lines in the script. A dependency is declared explicitly, for example:

Kotlin
tasks.register("B") {
    dependsOn("A")
}

When you ask Gradle to run a task, it builds the graph from the requested tasks and their declared relationships (dependsOn, plus dependencies inferred when one task consumes another's output). If task B depends on task A, A runs first. Independent tasks can be optimised or parallelised.

The Gradle build lifecycle: initialization โ†’ configuration โ†’ execution

Every build goes through three phases:

Initialization
Configuration
Execution

It is worth separating two things that are easy to confuse. The order of tasks is set by the relationships between tasks. The declared inputs and outputs of a task serve a different purpose โ€” they let Gradle determine whether a task needs to run at all, or whether its previous result can be reused.

The most common beginner trap: code in the script runs during the configuration phase, before any task starts. Something that "looks" like it does work may fire at the wrong point in the build lifecycle.

Dependency management

For a JVM developer this is one of Gradle's most visible features. You simply declare a library:

Kotlin
dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
}

and Gradle downloads it and resolves its transitive dependencies โ€” the libraries that library itself needs. One declaration pulls in a whole graph:

Plaintext
Your application
      โ”‚
      โ–ผ
spring-boot-starter-web
      โ”œโ”€โ”€ Spring MVC
      โ”œโ”€โ”€ Jackson
      โ”œโ”€โ”€ Tomcat (embedded server)
      โ””โ”€โ”€ โ€ฆ further dependencies

Gradle automatically downloads, caches and resolves this graph, reaching into repositories such as Maven Central. For larger projects it also offers version catalogs (libs.versions.toml), which centralise version numbers and eliminate version drift across modules.

Multi-module projects: the modular monolith

Large Spring, Java or Kotlin applications are rarely a single big module. A common pattern is the modular monolith โ€” one build and one deployable artifact, but the code split into several modules with clear boundaries. Gradle calls this a multi-project build.

A typical structure looks like this:

Plaintext
backend/
โ”œโ”€โ”€ app/                  # runnable module (wires everything together)
โ”œโ”€โ”€ modules/
โ”‚   โ”œโ”€โ”€ catalog/
โ”‚   โ”œโ”€โ”€ identity/
โ”‚   โ”œโ”€โ”€ purchase/
โ”‚   โ””โ”€โ”€ search/
โ”œโ”€โ”€ platform/             # shared conventions and versions
โ””โ”€โ”€ settings.gradle.kts

Each directory is a separate subproject, registered in a single settings.gradle.kts:

Kotlin
rootProject.name = "backend"
include("app", "modules:catalog", "modules:identity", "modules:purchase", "modules:search")

One module depends on another through a project dependency โ€” instead of repository coordinates you point to the subproject path:

Kotlin
// modules/purchase/build.gradle.kts
dependencies {
    implementation(project(":modules:catalog"))
}

Gradle works out the build order (catalog first, then purchase) and adds the dependent module's compiled classes to the classpath.

The heart of "communicating through a public API" is the java-library plugin and the distinction between two configurations:

  • api โ€” the dependency (and its types) is exposed to the module's consumers: it lands on their compile classpath. This is the module's public API.
  • implementation โ€” the dependency stays internal: it does not leak to consumers. It is an implementation detail.

The documentation advises: prefer implementation, and use api only when a type appears in the module's public interface (in method signatures, public fields, superclasses).

This keeps module boundaries tight: purchase sees only what catalog deliberately exposed, not its internal libraries. A useful side effect is faster builds โ€” a change in an implementation dependency does not force consumers to recompile.

Deployment note: a modular monolith is one artifact โ€” a release always covers everything, even when only one module changed. To deploy modules independently they must become separately runnable services (the microservices route).

The Gradle Wrapper

The Gradle Wrapper is the recommended way to run Gradle: a small launcher committed to the repository that runs a declared version of Gradle, downloading it beforehand if necessary. With it you don't need Gradle installed globally โ€” cloning the project is enough.

The Wrapper is not one file but a set of four, committed alongside the code:

The four Wrapper files:

gradlewlauncher script for Linux/macOS
gradlew.batthe Windows equivalent
gradle/wrapper/gradle-wrapper.jara small program that downloads and installs the correct Gradle version
gradle/wrapper/gradle-wrapper.propertiesconfiguration, including the distribution URL (distributionUrl) with the version number

On first run, ./gradlew reads the version from gradle-wrapper.properties, downloads that specific Gradle distribution from the distributionUrl, and stores it in a local cache. Later runs use the cached version and download nothing. That is why a build is launched through the wrapper:

Bash
./gradlew build

rather than relying on a system-wide Gradle:

Bash
gradle build

You bump the version with a single command that updates gradle-wrapper.properties:

Bash
./gradlew wrapper --gradle-version 9.0

The result: every developer and every CI server uses exactly the same version of Gradle, with no manual installation and no "works on my machine." That is why the wrapper files are committed to version control and โ€” per the documentation โ€” never edited by hand.

Performance: what Gradle can skip

Gradle's biggest practical edge is how much work it can skip. Four independent mechanisms make that possible โ€” from a single task all the way up to the whole configuration phase.

Up-to-date checks

For every task Gradle knows its declared inputs (source files, properties, classpath) and outputs. Before running a task it checks whether any of them changed since last time. If not, it marks the task UP-TO-DATE in the log and skips it. This works within a single workspace and is the basis of incremental builds: a rebuild runs only the tasks whose inputs changed, skipping the rest as up-to-date: re-running build with no changes does almost nothing.

Build cache

The build cache reaches beyond a single workspace. From a task's inputs Gradle computes a cache key. If a result for that key has already been computed somewhere โ€” locally or in a remote HTTP cache shared between developers and CI servers โ€” Gradle pulls the ready-made output instead of executing the task (logged as FROM-CACHE). The upshot: a task someone else or CI already ran for the same inputs need not run on your machine. Even switching back and forth between Git branches can reuse cached results.

Configuration cache

This one operates a level higher โ€” not on task execution but on the configuration phase itself. Gradle serializes its result (the task graph and task state), so on the next run โ€” as long as the build logic and inputs haven't changed โ€” it skips re-evaluating the scripts entirely and jumps straight to execution. For large projects, where configuration alone takes seconds, that is a noticeable saving. It works independently of up-to-date checks, which happen only during execution.

Gradle Daemon

The daemon: a process that runs continuously in the background, serving requests as they come without being started from scratch each time is a long-lived background process kept alive between builds. Starting a Java Virtual Machine and warming it up (JIT, loading Gradle itself) is expensive โ€” the daemon keeps it hot, so subsequent builds skip that overhead and start faster. It is enabled by default.

Gradle vs Maven vs Ant

The three best-known members of the JVM build world neatly illustrate how the approach evolved:

DimensionAntMavenGradle
ConfigurationXMLXMLKotlin/Groovy DSL
Styleimperativedeclarative / convention-baseddeclarative + programmable
Dependenciesexternal (Ivy)built-inbuilt-in
Build modeltargetslifecycle phasestask graph (DAG)
Flexibilityhighlowerhigh
Conventionslowhighhigh (via plugins)

Each tool solved its predecessor's pain point โ€” the easiest way to understand them is chronologically.

Ant

Ant (2000) was the first widely used build tool for Java โ€” the JVM world's answer to make. It describes a build imperatively: in a build.xml file you define "targets" (compile, jar, clean) and spell out every step yourself โ€” what to compile, what to copy, what to package. This gives full control, but every project invents its own structure from scratch and the files grow quickly. Ant also has no built-in dependency management โ€” you add it with a separate tool (Apache Ivy). Today it is mostly seen maintaining older systems.

Maven

Maven (2004) was a reaction to Ant's verbosity and bet on convention over configuration: it imposes a standard directory layout and a declarative pom.xml in which you describe what the project is, not how to build it. It introduced built-in dependency management with the Maven Central repository and a rigid build lifecycle โ€” an ordered sequence of phases (validate โ†’ compile โ†’ test โ†’ package โ†’ install โ†’ deploy). The result: Maven projects look alike and are predictable, which shortens onboarding. The cost is limited flexibility โ€” the moment you step outside the standard lifecycle you start fighting the tool, and custom logic written in XML becomes awkward.

Gradle

Gradle (2008) takes the best of both worlds: it keeps Maven's conventions and declarative dependency model, but instead of XML it offers a programmable DSL (Groovy or Kotlin) and a model built on a task graph rather than fixed phases. On top of that come the performance mechanisms โ€” incremental builds, the build and configuration caches, and the daemon. The result is usually faster builds and far more flexibility, at the cost of a steeper learning curve and less of the rigidity that enforced uniformity across Maven projects.

Which one to choose today?

For new JVM projects, and especially Android (where Gradle is the default), Gradle is the natural choice. Maven still makes sense for teams that value its predictability and have simple, standard build needs. Ant today is mostly about maintaining existing, older builds.

Key limitations and challenges

Gradle's flexibility comes at a cost. Because a build script is a programmable DSL, complex builds can become hard to understand and maintain โ€” it is easy to write logic that only its author later understands. Maven, for all its rigidity, is often more predictable in this respect.

The second challenge is the learning curve โ€” alongside the phase trap mentioned above comes a wealth of plugins and APIs that have changed between major versions. Migrations between major releases (for instance to the Gradle 9.x series, whose 9.0 release arrived in July 2025) require care.

One myth is worth dispelling, though: most of the performance mechanisms โ€” up-to-date checks, the build cache, the configuration cache and the daemon โ€” are built into Gradle itself and free. Commercial build-analytics tooling helps only with diagnosis and optimisation at large scale, but Gradle is fast without it too.

Why does it matter?

Gradle became a standard not through marketing, but through a single strategic decision โ€” Google's choice of it as Android's build system. That adoption turned knowing Gradle from a niche "build engineer" skill into a competency almost every JVM and mobile developer eventually acquires.

The deeper reason for its significance is architectural. Gradle popularised the idea that a build is not a sequence of commands executed one after another, but a dependency graph that an engine can analyse, optimise and cache. It is the same pattern that powers modern build tools in other ecosystems (such as Bazel or Nx). As repositories grow and teams expect CI to finish in minutes, mechanisms like incremental builds and a shared cache stop being a luxury and become a condition for productivity.

Sources

  • Gradle โ€” Gradle User Manual (overview) โ€” link
  • Gradle โ€” Gradle Basics โ€” link
  • Gradle โ€” Build Lifecycle โ€” link
  • Gradle โ€” Dependency Management Basics โ€” link
  • Gradle โ€” Build Cache โ€” link
  • Gradle โ€” Configuration Cache โ€” link
  • Android Developers โ€” Configure your build โ€” link
  • Wikipedia โ€” Gradle โ€” link
Share this insight