Chapter 24Lesson 01~235 minutes

Incremental Builds, Configuration Cache, Build Cache, Remote Cache, and Reproducibility: Concepts, Architecture, and Mental Model

Separate workspace up-to-date state, reusable task-output cache entries, configuration-phase snapshots, remote cache trust, and byte-for-byte reproducibility before enabling any optimization.

Gradle 9.7.1UP-TO-DATEBuild CacheConfiguration CacheReproducibility

Learning objectives

  • Distinguish current-workspace UP-TO-DATE state from reusable Build Cache entries and Configuration Cache snapshots.
  • Explain the inputs that influence a task Build Cache key and why undeclared inputs can create incorrect cache hits.
  • Explain path sensitivity and relocatability before attempting cross-workspace cache reuse.
  • Describe what Configuration Cache stores, what invalidates it, and why serialized task state is security-sensitive.
  • Define reproducibility as independently regenerated artifact identity rather than “the warm build succeeded twice”.

1. The practical problem: fast can be wrong

Chapter 17 made task inputs and outputs explicit. Chapter 23 made the JVM/toolchain identity explicit. Chapter 24 asks a harder operational question: when is Gradle allowed to avoid work or reuse work done elsewhere?

A fast result is only valuable if it is the result the declared source, configuration, toolchain, and dependency state require. If a task reads one undeclared file, a cache can faithfully reuse the wrong answer. If an archive embeds timestamps or an absolute workspace path, identical source can produce different bytes. If a shared cache accepts writes from untrusted machines, a cache hit becomes a supply-chain event.

Do not treat “cached” as “trusted.” The build cache is distributed build state. Correctness comes from accurate task inputs/outputs and deterministic task implementations; trust additionally depends on who can write cache entries and how cache transport/storage is protected.

2. One build, several independent reuse layers

Layer What state it reuses What proves it What it must not hide
Incremental / up-to-date Outputs already present in the current project workspace. Task outcome UP-TO-DATE; declared input/output snapshot matches. Undeclared inputs, overlapping outputs, source mutation, or hidden environment dependence.
Build Cache Task outputs created by another execution with the same cache key. Task outcome FROM-CACHE; output restored after clean. Non-repeatable output, absolute-path keys, untrusted cache writers, secrets embedded in outputs.
Configuration Cache Configured task graph/state and configuration inputs for a requested task set. First run stores; compatible later run reuses and skips configuration. Unsupported build-model access at execution, captured mutable state, or sensitive task fields.
Dependency cache Downloaded dependency artifacts/metadata under Gradle User Home. Resolution works without repeated download when cached. It is not a build-output cache and is not evidence that tasks are correct.
Reproducible output No reuse claim by itself; it is an identity property of produced bytes. Two independent builds yield the same SHA-256 and sensible metadata. Warm-cache-only comparisons, embedded timestamps/paths, nondeterministic generators.
Cache, execution, and reproducibility flow
flowchart TD
  S["Source + declared task inputs"] --> K["Task state / cache key"]
  K --> U{"Outputs already here?"}
  U -->|yes| UT["UP-TO-DATE"]
  U -->|no| B{"Build-cache entry?"}
  B -->|yes| FC["FROM-CACHE restore"]
  B -->|no| EX["Execute task"]
  C["Build scripts + config inputs"] --> CC["Configuration Cache"]
  CC --> TG["Configured task graph"]
  TG --> EX
  EX --> O["Outputs / JAR"]
  FC --> O
  UT --> O
  O --> H["Independent SHA-256 comparison"]
            

The arrows deliberately represent different mechanisms. Source and task inputs influence task state. Existing outputs may make a task UP-TO-DATE. If outputs are absent, a build-cache key can locate reusable outputs and produce FROM-CACHE. Separately, build scripts and configuration inputs determine whether Gradle can reuse a configured task graph. Reproducibility is checked after output production by comparing independently generated bytes.

3. Build Cache key: identity before reuse

For a cacheable task, Gradle derives a key from the task implementation/action implementations, output property names, and declared input property names and values. Two executions can reuse outputs only when their relevant cache identity matches. This is why “I changed a file but Gradle reused the old output” is usually not a cache bug first; it is evidence to inspect whether the changed file was declared as an input.

Custom tasks are not automatically safe to cache merely because they have an output file. A task must have repeatable outputs, stable declared inputs, and suitable cacheability metadata such as @CacheableTask. Built-in Gradle tasks may already implement these contracts.

4. Relocatability: paths can accidentally become inputs

Cross-workspace reuse only works when irrelevant workspace paths do not change task identity. For file inputs, Gradle uses path sensitivity to decide whether absolute path, relative path, name-only, or no path should matter. An input with absolute path sensitivity is non-relocatable: /agent/a/repo/file.txt and /agent/b/repo/file.txt produce different path identity even when the file bytes are identical.

For repository-relative source/config files, PathSensitivity.RELATIVE is commonly appropriate because the path inside the project matters while the checkout root should not. Path normalization is a correctness decision: do not choose a weaker sensitivity only to increase cache hits if the path actually changes semantics.

5. Configuration Cache: a different cache with a different key

Gradle’s build lifecycle still has initialization, configuration, and execution. With the Configuration Cache enabled, Gradle can serialize the configured tasks and their reachable state plus a fingerprint of configuration inputs. On a compatible later invocation of the same requested work, Gradle can deserialize that state and skip the normal configuration phase.

Configuration inputs include build/settings scripts, build logic, relevant Gradle configuration files, files and filesystem state read during configuration, system properties, environment variables, and provider/value-source results obtained at configuration time. This is why reading the environment eagerly in a build script can make cache validity depend on more machine state than intended.

Current status: opt-in and local-only. In Gradle 9.7.1 the Configuration Cache is not enabled by default. It is stored under the build’s .gradle/configuration-cache and cannot currently be shared between developers or CI machines. Treat it separately from the local/remote Build Cache.

6. Configuration-cache security boundary

The Configuration Cache serializes complete scheduled-task state, including private fields and values reachable from those fields. Gradle encrypts cache entries on disk using a key associated with GRADLE_USER_HOME, but encryption does not make careless secret capture safe. A system property or environment variable consumed during configuration becomes part of the configuration fingerprint/state story.

Prefer lazy wiring: obtain sensitive values through providers and let a task read them at execution only when required. Keep repository credentials out of source-controlled build scripts. Restrict both the project cache directory and the corresponding encryption key. A CI cleanup policy must understand that configuration-cache state is potentially sensitive build state.

7. Local versus remote Build Cache trust

Gradle has a built-in local directory cache and can also talk to a remote HTTP build cache. When both are available, Gradle can reuse local entries first and then remote entries. The remote cache is a collaboration boundary: an entry may have been produced on another machine and can replace expensive local task execution.

A common production trust pattern is trusted CI writes, developers read. Gradle’s remote cache push is disabled by default; keep that conservative posture unless the writer population and cache backend are governed. Do not set allowUntrustedServer merely to silence TLS failures—it explicitly weakens server authentication.

8. Reproducibility: regenerate, then compare

For archive tasks, Gradle 9+ defaults help: file timestamps are not preserved and file ordering is reproducible. Those defaults improve JAR/ZIP stability, but they do not guarantee an entire build is reproducible. Generated content can still contain clocks, random identifiers, hostnames, absolute paths, locale-dependent output, or toolchain differences.

A credible test uses separate project paths and controlled inputs, preferably with output/configuration caches disabled for the final identity run. Then compare SHA-256 values and inspect archive contents. If the bytes differ, preserve both artifacts and inspect metadata rather than immediately deleting caches.

9. Read-only inspection before mutation

# Use the verified project Wrapper from Chapter 15 onward.
./gradlew --version
./gradlew tasks --all
./gradlew properties

# Observe task graph/outcomes without enabling persistent cache settings yet.
./gradlew renderGreeting --dry-run
./gradlew renderGreeting --info

# Inspect project-local Gradle state only; do not delete normal ~/.gradle.
find .gradle -type d 2>/dev/null | sort | head -n 40 || true
find build -type f 2>/dev/null | sort | head -n 40 || true

These commands identify wrapper/JVM state, selected tasks, and current project-local state. A production incident starts from this evidence before toggling caches or cleaning anything.

10. DevOps operating rule

Think of caches as acceleration layers on top of a correct declarative model. A CI platform may restore dependency caches, use a shared build cache, reuse configuration locally on an agent, and publish artifacts—but each layer has separate invalidation and trust rules. The safe sequence is: establish deterministic uncached behavior, declare state accurately, add reuse, measure, then periodically re-prove from clean state.

Knowledge check

What is the key observable difference between UP-TO-DATE and FROM-CACHE?

Does enabling the Configuration Cache enable the Build Cache too?

Why can PathSensitivity.ABSOLUTE block cross-checkout reuse?

Why is one successful warm build not proof of reproducibility?

What is the safer default for shared remote-cache writes?

Where does Chapter 25 go next?

Official references and version notes

Version-sensitive behavior was rechecked against current Gradle primary documentation on 2026-08-24. The mandatory path uses Gradle 9.7.1 through the previously verified Wrapper, JDK 21 as the Gradle runtime/compiler toolchain, Java 17 as the project target, isolated lab state, and no paid service. Both the Build Cache and Configuration Cache are opt-in in Gradle 9.7.1. The Configuration Cache is local-only and cannot currently be shared across developers or CI machines. Remote HTTP build-cache examples are explanatory only; the hands-on cross-workspace exercise uses a disposable shared directory cache so no server or credentials are needed.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.