skip to content

What makes a task relocatable, and how can wrong input normalization cause false cache hits or chronic misses across machines?

level: seniorimportance: should knowfreq 40%

answer

  1. relocatable = key independent of abs path
  2. @PathSensitive(RELATIVE) default ABSOLUTE
  3. under-normalized -> chronic cross-machine miss
  4. under-declared -> false hit (correctness bug)
  5. reproducible archives: no timestamps, sorted order

basics

~20 s

A relocatable task produces the same key regardless of the project's absolute path. Wrong path-sensitivity puts absolute paths in the key, causing misses on other machines; missing/undeclared inputs cause false hits where stale outputs are wrongly restored.

solid answer

~50 s

**Relocatability** means a task's cache key is independent of where the project lives on disk, so an entry produced in `/home/ci/checkout` can be reused at `/Users/dev/repo`. It's achieved by declaring input files with `@PathSensitive(RELATIVE)` (or `NAME_ONLY`/`NONE`) and outputs that don't bake absolute paths. If you forget normalization, the absolute path is hashed into the key, so every machine with a different checkout path gets a **chronic miss** — caching appears 'on' but never hits cross-machine. The opposite failure is the **false hit**: a task depends on something it didn't declare (an env var, a sibling file, the JDK minor version), so two genuinely different scenarios compute the *same* key. Gradle restores stale outputs that are wrong for the current context. False hits are worse than misses — they're silent correctness bugs. Diagnose both with `-Dorg.gradle.caching.debug=true`: chronic misses show diverging path hashes; false hits show a *missing* component you'd expect to differ. Fix with correct normalization, complete input declaration, and reproducible outputs.

code

kotlin · 9 lines
kotlin
@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE) // <- makes the task relocatable
abstract val templates: ConfigurableFileCollection

// reproducible outputs so downstream tasks cache reliably
tasks.withType<Jar>().configureEach {
    isPreserveFileTimestamps = false
    isReproducibleFileOrder = true
}

go deeper

for a junior

Know that absolute paths can sneak into the key and that all real inputs must be declared.

for a middle

Explain @PathSensitive options and how under-normalization causes cross-machine misses.

for a senior

Contrast chronic misses vs false hits, identify undeclared inputs, and enforce reproducible outputs.

for a principal

Define org-wide normalization/reproducibility standards and review processes so cache correctness isn't left to chance per task.

## Relocatability: the key must not encode the checkout path The build cache only pays off when an entry computed in one location is reusable in another — your laptop, a teammate's, a fresh CI agent. A task is **relocatable** when its cache key does not depend on the absolute filesystem path of the project. Gradle achieves this through **input normalization**: - `@PathSensitive(PathSensitivity.RELATIVE)` — the key reflects each input file's *relative* path within its root plus content. Most source-consuming tasks want this. - `@PathSensitive(NAME_ONLY)` — only file name + content matters. - `@PathSensitive(NONE)` — only content; paths ignored entirely (e.g. a bundle of resources where location is irrelevant). - `@Classpath` / `@CompileClasspath` — order-insensitive, jar-internal-timestamp-insensitive classpath hashing. If you declare an `@InputFiles` without path sensitivity, Gradle uses ABSOLUTE by default — the machine-specific path enters the key, and **no two checkouts agree**, so the cross-machine hit rate is ~0 even though caching is enabled. ## The two failure modes ### Chronic miss (under-normalized) Absolute paths or jar timestamps leak into the key. Symptoms: local builds may hit, but CI and teammates always execute. Debug log shows the input file hash differing only because the path prefix differs. ### False hit (under-declared) The task's real output depends on an input it never declared: a `System.getenv("PROFILE")`, a generated file read directly, or non-ABI bytecode differences. Two different contexts hash to the *same* key, and Gradle restores the wrong outputs. This is a **correctness** bug, not a performance one — a build can pass with stale artifacts. There is no automatic detection; you find it by reasoning about whether *every* thing the task reads is a declared input, optionally validated by Gradle's runtime API for tracking and by `--scan` input audits. ## Making outputs reproducible too Even with correct inputs, *non-reproducible outputs* (jars with embedded timestamps, file order) undermine downstream tasks' caching. Set: ```kotlin tasks.withType<AbstractArchiveTask>().configureEach { isPreserveFileTimestamps = false isReproducibleFileOrder = true } ``` ## Diagnosis recap - Chronic cross-machine miss -> check path sensitivity; look for absolute paths in the debug key trail. - Suspicious correctness after a hit -> hunt for undeclared inputs; the debug trail will be *missing* a component that should distinguish the two cases. Relocatability + complete declaration + reproducible outputs are the three pillars that make cache keys both *stable* (hit often) and *correct* (never falsely).

  • Why is a false cache hit more dangerous than a cache miss?
    A miss only costs time — the task re-runs correctly. A false hit silently restores wrong outputs, so the build is fast but incorrect, and it can ship broken artifacts.
  • Caching is enabled but CI never hits while local re-builds do. What do you check first?
    Path sensitivity. The absolute checkout path almost certainly leaks into the key (ABSOLUTE default), so the differing CI path makes every key unique. Apply @PathSensitive(RELATIVE).
  • How do non-reproducible jars hurt caching even with correct inputs?
    Their content hash differs run-to-run, so tasks consuming them as @InputFiles/@Classpath get different keys, cascading misses downstream. Disable timestamps and fix file order.

Relocatability is like a recipe written with 'top shelf' instead of 'GPS coordinates of my pantry' — anyone can follow it. An undeclared input is a secret ingredient the recipe forgets to list, so two cooks get the same recipe but different dishes.

saying these in an interview costs you the question

  • Treating a false hit as harmless because the build is green
  • Assuming caching is reusable cross-machine without any path normalization
  • Ignoring reproducibility of produced jars

context