skip to content

On the same project, the same task can show UP-TO-DATE on one run and FROM-CACHE on another. Explain the difference and when each occurs.

level: seniorimportance: should knowfreq 55%

answer

  1. up-to-date = outputs already valid locally
  2. from-cache = outputs absent, restored from store
  3. up-to-date checked first, then cache, then run
  4. cache shines on fresh/CI workspaces
  5. caching needs opt-in; up-to-date doesn't

basics

~20 s

UP-TO-DATE: the correct outputs are already on disk locally and inputs are unchanged, so nothing happens. FROM-CACHE: the outputs are missing locally, so Gradle restores a matching entry from the build cache instead of running the task.

solid answer

~40 s

Both outcomes mean the task didn't really execute, but they answer different questions. **Up-to-date checking is local and answers 'are my current outputs still valid?'** — if inputs and the existing outputs both match the last run, the task is `UP-TO-DATE` and nothing is touched. **The build cache answers 'have these exact outputs been produced before, anywhere?'** — it's consulted only when the outputs are *absent* locally (after `clean`, on a fresh checkout, or on another machine/CI agent). If a cache entry keyed by the task's input fingerprint exists, Gradle restores the outputs and reports `FROM-CACHE`. Order of checks: up-to-date first; only if that fails (outputs missing/stale) does Gradle try the cache; only if the cache misses does the task actually run. Caching must be enabled (`org.gradle.caching=true`) and the task must be cacheable.

code

bash · 6 lines
bash
# Populate cache, outputs present locally
$ ./gradlew build --build-cache        # ... :test executed
# Run again, nothing changed
$ ./gradlew build --build-cache        # > Task :test UP-TO-DATE
# Wipe local outputs, but cache still holds them
$ ./gradlew clean build --build-cache  # > Task :test FROM-CACHE

go deeper

for a junior

Know they both mean the task didn't really run; UP-TO-DATE = already there, FROM-CACHE = fetched.

for a middle

State the decision order and give the clean-vs-no-clean example that flips one to the other.

for a senior

Reason about why CI relies on FROM-CACHE while local dev rides UP-TO-DATE, and diagnose tasks that unexpectedly execute on CI.

for a principal

Decide remote-cache strategy and cacheability requirements so the org gets FROM-CACHE wins across machines, not just local UP-TO-DATE.

## UP-TO-DATE vs FROM-CACHE They look like twins in the console but represent two distinct mechanisms. ### Up-to-date checking (local incrementality) - **Scope:** this machine, this checkout. - **Question:** "Are the outputs already sitting in my project still correct?" - **When it succeeds:** inputs unchanged **and** the existing output files are unchanged → `UP-TO-DATE`, nothing copied. - **When it fails:** an input changed, or the outputs are gone/modified → move on. ### The build cache (cross-build/cross-machine reuse) - **Scope:** a key→outputs store (local `~/.gradle/caches` and/or a shared remote cache). - **Question:** "Has a task with *this exact input fingerprint* ever produced outputs we saved?" - **When it's consulted:** only when up-to-date **fails** — i.e. the outputs aren't already valid on disk. - **When it succeeds:** a matching entry exists → outputs are **unpacked into place** and the task reports `FROM-CACHE`. ### The decision order ```text for each task: if inputs & local outputs unchanged -> UP-TO-DATE (skip) else if caching on & cache entry hit -> FROM-CACHE (restore) else -> execute (run actions) ``` ### Concrete scenarios | Situation | Outcome | |---|---| | Run `build`, change nothing, run `build` again | `UP-TO-DATE` (outputs still on disk) | | Run `clean build --build-cache`, then `build --build-cache` again without clean | second run `UP-TO-DATE` | | Run `clean build --build-cache` (cache already populated) | `FROM-CACHE` (clean removed local outputs) | | Fresh `git clone` on a new machine, run with shared cache | `FROM-CACHE` (no local history *or* outputs) | ### Why the distinction matters operationally - **Local dev** lives mostly on UP-TO-DATE — fast because nothing is even copied. - **CI and clean builds** rely on FROM-CACHE — fresh workspaces have no up-to-date history, so the cache is what keeps them fast. - Seeing a task you expect to cache show up as *executed* (no suffix) on CI signals it isn't cacheable or its inputs are unstable across machines. ### Enabling requirements - `org.gradle.caching=true` (in `gradle.properties` or `--build-cache`). - The task must be marked cacheable (built-in compile/test tasks are; custom tasks need `@CacheableTask`). Up-to-date checking, by contrast, needs no opt-in — it's always on for tasks that declare inputs and outputs.

  • Which check does Gradle perform first, up-to-date or cache?
    Up-to-date first. The cache is only consulted when the outputs aren't already valid locally; if the cache also misses, the task executes.
  • Why does the build cache matter more on CI than locally?
    CI agents typically start from clean workspaces with no prior up-to-date history, so nothing is UP-TO-DATE. A shared cache lets them restore outputs FROM-CACHE that other builds already produced.
  • A test task is UP-TO-DATE locally but executes (no suffix) on CI. What's the likely cause?
    CI's fresh workspace had no local outputs, and either caching is off, the task isn't cacheable, or an input differs across machines (env-sensitive input), so there was no cache hit.

UP-TO-DATE is finding the leftovers already plated in your fridge; FROM-CACHE is the fridge being empty so you reheat a portion from the shared freezer that someone batch-cooked earlier.

saying these in an interview costs you the question

  • Saying the cache is checked before up-to-date — it's the reverse.
  • Claiming up-to-date requires enabling org.gradle.caching — only the build cache does.
  • Asserting FROM-CACHE copies nothing — it unpacks restored outputs into place.

context