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?
answer
- daemon = JVM warmth, not outputs
- UP-TO-DATE = incremental, FROM-CACHE = build cache
- output reuse works with --no-daemon
- warm daemon still re-runs changed tasks
- two orthogonal speedups
basics
~20 sWrong: 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 sThey'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# 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 compileJavago deeper
Know the daemon makes startup faster but doesn't cache task outputs; that's the build cache/incremental.
Separate UP-TO-DATE/FROM-CACHE (output reuse) from JVM warmth (daemon) cleanly.
Articulate the orthogonality and demonstrate each independently (e.g. output reuse under --no-daemon).
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.