skip to content

A teammate says 'the daemon makes my second build fast because it caches the outputs.' What's wrong with that, and what does the daemon actually reuse?

level: seniorimportance: should knowfreq 35%

answer

  1. daemon = JVM warmth, not outputs
  2. UP-TO-DATE = incremental, FROM-CACHE = build cache
  3. output reuse works with --no-daemon
  4. warm daemon still re-runs changed tasks
  5. two orthogonal speedups

basics

~20 s

Wrong: task-output reuse (UP-TO-DATE / FROM-CACHE) comes from incremental builds and the build cache, not the daemon. The daemon reuses JVM warmth — loaded classes, JIT-compiled hot code, and in-memory model — to skip startup, not to skip task work.

solid answer

~50 s

They've conflated two different speedups. **Task-output reuse** — tasks reported `UP-TO-DATE` (incremental, via task input/output snapshots) or `FROM-CACHE` (the build cache) — is what avoids *redoing work*, and it has nothing to do with the daemon; it works even with `--no-daemon`. The **daemon** reuses something else entirely: **JVM warmth**. Across invocations the same long-lived JVM keeps its classes loaded, its hot methods JIT-compiled to native code, and parts of Gradle's project model warm in memory. That's why the *start-up* and execution feel faster on a reused daemon — not because tasks are skipped, but because the engine is already hot. The clean mental model: build cache / incremental = *don't redo work that's already done*; daemon = *don't re-boot and re-warm the JVM*. Both make the second build faster, but for orthogonal reasons, and you can have either without the other.

code

bash · 5 lines
bash
# Output reuse without a daemon — proves the daemon isn't what caches outputs
./gradlew build --no-daemon   # tasks still report UP-TO-DATE / FROM-CACHE if unchanged

# Warm daemon, changed inputs -> task re-runs anyway (daemon ≠ output cache)
./gradlew compileJava

go deeper

for a junior

Know the daemon makes startup faster but doesn't cache task outputs; that's the build cache/incremental.

for a middle

Separate UP-TO-DATE/FROM-CACHE (output reuse) from JVM warmth (daemon) cleanly.

for a senior

Articulate the orthogonality and demonstrate each independently (e.g. output reuse under --no-daemon).

for a principal

Ensure teams attribute build-speed wins correctly so investment goes to the right lever (caching infra vs daemon/warm runners).

## Two independent speedups, often confused The statement mixes up **output reuse** and **process warmth**. They both make a repeat build faster, but they are separate systems with separate mechanisms. ## What actually reuses task outputs - **Incremental build (UP-TO-DATE):** before running a task, Gradle snapshots its declared **inputs and outputs**. If nothing changed since last time, the task is skipped and reported `UP-TO-DATE`. This state lives in the project's `.gradle` directory on disk. - **Build cache (FROM-CACHE):** for `@CacheableTask`s, Gradle computes a hash of the task's inputs and looks up the matching outputs in a **local and/or remote cache**, restoring them instead of executing. This works across machines and across clean checkouts. Crucially, **both work with `--no-daemon`** — they're about not redoing work, and they persist on disk / in a cache store, independent of any running process. ## What the daemon actually reuses The daemon reuses **JVM warmth**, i.e. the state of the running process: - **Loaded classes** — Gradle, plugins, and dependencies are already in the JVM; no re-loading. - **JIT-compiled hot code** — the JIT has already promoted hot methods to optimized native code, so they run at full speed instead of being interpreted. - **In-memory model** — parts of the configured project model and other in-memory state stay hot. This is why a reused daemon starts builds faster and executes engine code faster — but it does **not** skip task work. Even on a perfectly warm daemon, a task whose inputs changed will run again. ## A concrete way to see the difference ```bash # Daemon warm, but task inputs changed -> task RE-RUNS (no output reuse) ./gradlew compileJava # warm engine, but recompiles changed sources # Even with --no-daemon, unchanged task -> UP-TO-DATE / FROM-CACHE ./gradlew compileJava --no-daemon # cold JVM, yet skips work if nothing changed ``` The first shows warmth without output reuse; the second shows output reuse without daemon warmth. ## The correct mental model | Mechanism | Reuses | Avoids | Works with --no-daemon? | |---|---|---|---| | Incremental build | input/output snapshots | redoing unchanged tasks | Yes | | Build cache | hashed task outputs | redoing tasks done elsewhere | Yes | | Daemon | JVM warmth (classes/JIT/model) | re-booting & re-warming JVM | (it *is* the daemon) | So correct the teammate: the daemon doesn't cache outputs — it keeps the JVM hot. Output reuse is a different, complementary system.

  • Can you get task-output reuse without the daemon?
    Yes. Incremental build (UP-TO-DATE) and the build cache (FROM-CACHE) work even with --no-daemon — they're about not redoing work and are stored on disk / in a cache, independent of the process.
  • On a warm daemon, will a task whose source changed be skipped?
    No. Warmth only speeds engine/startup; if the task's inputs changed it re-runs. Skipping is decided by incremental/build-cache mechanisms, not the daemon.

Incremental/build cache is keeping yesterday's finished dishes in the fridge; the daemon is keeping the oven preheated. A hot oven doesn't mean dinner is already cooked.

saying these in an interview costs you the question

  • Saying the daemon caches or stores task outputs.
  • Implying you lose UP-TO-DATE/FROM-CACHE behavior when you pass --no-daemon.
  • Claiming a warm daemon means tasks are skipped regardless of input changes.

context