skip to content

Serializing The Task Graph

How Gradle serializes the resolved task graph, keyed by build inputs such as task requests, properties, environment variables, and files, so configuration can be skipped entirely. Interviewers ask what invalidates that entry.

on this pageshow

questions

5

When the configuration cache is enabled, what does Gradle serialize and store at the end of the configuration phase, and what does that let it skip on the next build?

level: juniorimportance: must knowfreq 60%

answer

  1. three phases: init, configuration, execution
  2. caches the configuration-phase result
  3. serialized task graph + configured task state
  4. .gradle/configuration-cache/
  5. skips re-running build scripts on a hit

basics

~20 s

Gradle serializes the resolved task graph — the tasks to run plus their configured state — to disk. On a cache hit it loads that snapshot and skips the whole configuration phase, going straight to executing tasks.

solid answer

~40 s

A Gradle build has three phases: initialization, configuration, and execution. The configuration cache captures the result of the configuration phase — the fully resolved task graph: which tasks will run, their dependencies, and each task's configured input/output state (property values, resolved dependencies). It serializes this into a cache entry under `.gradle/configuration-cache/`. On the next invocation with the same inputs, Gradle deserializes the stored graph instead of re-running build scripts and re-configuring tasks, then proceeds directly to execution. This skips re-evaluating `settings.gradle(.kts)`, project build scripts, and all `tasks.register`/`configure` work, which is where large multi-project builds spend significant wall-clock time. It is distinct from the build (output) cache, which caches individual task *outputs*; the configuration cache caches the *configuration phase result*.

code

bash · 7 lines
bash
# First run computes and stores the graph
$ ./gradlew assemble
> Calculating task graph as no configuration cache is available for tasks: assemble

# Second run reuses it, skipping configuration
$ ./gradlew assemble
> Reusing configuration cache.

go deeper

for a junior

Name the three phases and say the configuration cache skips the configuration phase by reusing a serialized task graph.

for a middle

Distinguish configuration cache from build cache, and explain what 'configured task state' means (property values, resolved dependencies).

for a senior

Discuss when the savings matter (large multi-project builds, IDE sync, CI) and that both caches compose.

for a principal

Frame it as part of an org-wide build-performance strategy alongside remote build cache and Develocity, and the migration cost of making builds compatible.

## The three build phases Every Gradle build runs three phases: 1. **Initialization** — evaluates `settings.gradle(.kts)`, determines which projects make up the build. 2. **Configuration** — evaluates each project's build script, creating and configuring the `Task` objects and computing the **task graph** (the DAG of tasks and their dependencies). 3. **Execution** — runs the selected tasks in dependency order. The configuration phase runs **every invocation** by default, even for an incremental no-op build. In a large multi-project build this can cost seconds to tens of seconds. ## What the configuration cache stores When enabled, Gradle serializes the **result of the configuration phase** to disk: the resolved task graph and the configured state of every task that will execute — property values, resolved dependency sets, file collections, and so on. This serialized blob lives under `.gradle/configuration-cache/`. On a subsequent build with matching inputs, Gradle **deserializes** the graph and jumps straight to execution, skipping initialization and configuration entirely. It does not re-run your build scripts at all on a hit. ## Why this is more than the build cache People conflate two caches: - **Build cache** (a.k.a. output/remote cache): caches the *outputs* of individual `@CacheableTask` tasks keyed by their inputs, so an up-to-date task can be fetched instead of re-executed. - **Configuration cache**: caches the *configuration phase itself* — the task graph and configured task state — so the configuration phase is skipped. They are complementary: the configuration cache makes "deciding what to run" cheap; the build cache makes "running it" cheap. ## Practical effect ``` $ ./gradlew build Calculating task graph as no configuration cache is available... $ ./gradlew build Reusing configuration cache. ``` The second run never evaluates the build scripts.

  • How is the configuration cache different from the build (output) cache?
    The build cache stores individual task *outputs* keyed by task inputs; the configuration cache stores the *configuration-phase result* (the task graph and configured task state) so the configuration phase is skipped. They are complementary and can both be on at once.
  • Where does Gradle write the configuration cache entries?
    Under the project's `.gradle/configuration-cache/` directory by default; entries are keyed by the build's inputs.

saying these in an interview costs you the question

  • Saying the configuration cache stores task outputs — that is the build cache.
  • Claiming it skips execution — it skips configuration, then still executes tasks.

context

open as a page

What build inputs make up the configuration cache key, and what causes Gradle to discard the entry and recompute the task graph?

level: middleimportance: must knowfreq 55%

basics

~20 s

The key includes the set of requested tasks, plus the values of any system properties, environment variables, Gradle properties, and files read during configuration. Change any of these and the entry is invalidated, so Gradle recomputes the graph.

open as a page

Why must configuration-time values be captured through Provider/Property and lazy task inputs rather than eagerly, for the task graph to serialize correctly?

level: middleimportance: should knowfreq 45%

basics

~20 s

Because the configured task state must be serializable. Capturing values lazily through Provider/Property and declared task inputs lets Gradle store and restore them. Capturing a Project or other live objects in a closure makes the graph unserializable.

open as a page

A teammate reports the configuration cache never hits — every run prints 'Calculating task graph'. How would you diagnose whether it's a serialization failure or an input-invalidation issue?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Run twice with the identical command. If the store run reports configuration cache problems (e.g. unserializable state), it can't be reused — fix those. If there are no problems but it still misses, a tracked input (a property, env var, or file) is changing between runs.

open as a page

Walk through what happens on a configuration cache 'store' (miss) versus a 'load' (hit) run, and how Gradle decides which one occurs.

level: seniorimportance: should knowfreq 40%

basics

~20 s

On a miss, Gradle runs configuration, serializes the task graph to an entry keyed by the build inputs, then executes. On a hit, it finds an entry whose key matches the current inputs, deserializes the graph, and executes without re-running configuration.

open as a page