What does the @LocalState annotation declare on a task property, and why would you use it instead of @OutputDirectory?
answer
- intermediate, machine-specific scratch
- tracked for cleanup, excluded from cache packing
- deleted on cache-hit restore
- incremental-compilation analysis files
- not an output, not portable
basics
~10 s@LocalState marks a directory or file holding a task's intermediate, machine-specific state. Gradle tracks it for up-to-date checks but does NOT store it in the build cache, because it shouldn't be shared across machines.
solid answer
~40 s`@LocalState` declares a property pointing at intermediate state a task produces that is **not** a shareable output — typically incremental-compilation analysis files or scratch data tied to the local machine/filesystem. Like outputs, Gradle removes local state when the task is loaded from the build cache (so a cached run doesn't leave stale local state behind), but unlike `@OutputDirectory`, local state is **never packed into a cache entry**. That keeps cache entries portable: machine-specific paths or absolute references inside local state won't pollute what other machines download. Use it instead of `@OutputDirectory` when the directory speeds up *re-runs on this machine* but would be meaningless or harmful if shipped to another machine via the cache.
code
kotlin · 16 lines@CacheableTask
abstract class GenerateCode : DefaultTask() {
@get:InputDirectory
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val sources: DirectoryProperty
@get:OutputDirectory
abstract val generated: DirectoryProperty
// Incremental index that speeds local re-runs but must NOT be cached/shared.
@get:LocalState
abstract val analysisDir: DirectoryProperty
@TaskAction
fun run() { /* read analysisDir, write generated, update analysisDir */ }
}go deeper
Know that @LocalState marks intermediate, machine-local scratch data that Gradle tracks but does not put in the build cache.
Explain the two behaviors: excluded from cache packing AND deleted on cache-hit restore, and give the incremental-compilation example.
Contrast with @OutputDirectory (portable, packed) and @Internal (ignored), and explain why portability of cache entries motivates the distinction.
Discuss governance: when a custom plugin's task leaks machine-specific paths into cache entries, prescribe @LocalState as the fix and reason about cross-machine cache hit rates.
## What 'local state' means A task in Gradle declares **inputs** (things that, when changed, make the task stale) and **outputs** (the artifacts it produces). The build cache packs declared outputs into a cache entry keyed by a hash of the inputs, so another machine — or a later build — can download outputs instead of re-running the task. Some tasks also produce **intermediate state** that: - speeds up the *next* run on the same machine (e.g. an incremental-compilation analysis cache), but - is **not a real output** consumers want, and - is **not portable** — it may embed absolute paths or be tied to this filesystem. Declaring such a directory with `@OutputDirectory` would be wrong: it would get packed into the cache and shipped to other machines, where it is at best useless and at worst corrupting. Declaring it as a plain field (untracked) is also wrong: Gradle wouldn't clean it up when restoring from cache, leaving stale state behind. `@LocalState` is the middle ground. ## What @LocalState does ``` @get:LocalState abstract val analysisDir: DirectoryProperty ``` 1. **Tracked for cleanup, not for portability.** When the task's outputs are restored from the build cache (a cache hit), Gradle **deletes** any registered local-state directories first. This prevents a stale local-state cache from being mixed with freshly-downloaded outputs. 2. **Never stored in the cache entry.** Local state is excluded from what gets packed, so cache entries stay machine-independent. 3. **Not an output for up-to-date purposes in the consumer sense** — it does not become an input to downstream tasks. ## When to reach for it The canonical example is incremental compilation: the Java/Kotlin compile tasks keep an analysis/mapping file that makes the *next* incremental compile fast, but it's local scratch. Gradle's own `JavaCompile` uses local state for exactly this. If you write a code-generation or compilation-style task that keeps a private incremental index, declare it `@LocalState`. ## Relationship to @CacheableTask `@LocalState` only matters for **cacheable** tasks (tasks annotated `@CacheableTask` or otherwise cache-enabled). For a non-cacheable task there is no cache entry to exclude it from, and no cache-restore step to clean it during, so the annotation has no practical effect.
- What happens to a @LocalState directory when the task's outputs are loaded from the build cache?Gradle deletes the local-state directory before/while restoring outputs, so stale local state isn't combined with freshly downloaded cached outputs.
- Does @LocalState do anything for a task that isn't cacheable?Effectively no. With no cache entry to exclude it from and no cache-restore cleanup step, the annotation has no practical effect on a non-cacheable task.
Output files are the finished cake you can mail to anyone; local state is the flour-dusted mixing bowl that helps you bake faster next time but is useless to ship.
saying these in an interview costs you the question
- Saying local state IS stored in the build cache — it is explicitly excluded.
- Claiming @LocalState makes the directory a task output usable by downstream tasks.
- Confusing it with @Internal (untracked) — local state is still cleaned on cache restore, @Internal is fully ignored.