Which input/output annotations does Gradle use to build a cacheable task's key, and what does @Internal mean for caching?
answer
- @Input scalar, @InputFiles content
- @Classpath normalized, @Nested recurses
- @OutputFile/@OutputDirectory stored
- @Internal = ignored for key
- unannotated => validation warning
basics
~10 s@Input, @InputFile(s)/@InputDirectory, @Classpath, @Nested feed the cache key; @OutputFile(s)/@OutputDirectory declare what's stored. @Internal marks a property that is neither — it's excluded from the key.
solid answer
~50 sGradle derives a cacheable task's key from its **declared inputs** and stores its **declared outputs**. The vocabulary: `@Input` for serializable scalar properties; `@InputFile`, `@InputFiles`, `@InputDirectory` for file inputs (hashed by content + path sensitivity); `@Classpath`/`@CompileClasspath` for classpath-normalized file collections; `@Nested` to recurse into a nested object's own annotated properties. Outputs: `@OutputFile`, `@OutputFiles`, `@OutputDirectory`, `@OutputDirectories` — these define exactly what gets stored to and restored from the cache. Crucially, **every** property on a task type must be categorized: if a property is neither input nor output, you annotate it `@Internal` to tell Gradle to ignore it for the key (e.g. a logger, a service handle, a derived convenience getter). Leaving a property unannotated triggers a validation warning. Misusing `@Internal` on something that *does* affect output is dangerous — it drops a real input from the key and risks false cache hits.
code
kotlin · 12 lines@CacheableTask
abstract class Render : DefaultTask() {
@get:Input abstract val theme: Property<String>
@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val templates: ConfigurableFileCollection
@get:OutputDirectory abstract val outDir: DirectoryProperty
@get:Internal val clock: Clock get() = Clock.systemUTC()
}go deeper
Recognize @Input/@InputFiles/@OutputFile and that @Internal means 'ignore for caching'.
Map each property to the correct annotation, know @Nested recursion and the unannotated-property warning.
Reason about @Internal misuse causing false hits and about @Optional/@SkipWhenEmpty interactions.
Establish review standards so input declarations stay complete and @Internal is never a correctness escape hatch.
## The key is built from declarations For a cacheable task, Gradle snapshots the **declared inputs**, hashes them into the cache key, runs the task if no entry matches, and stores the **declared outputs**. Each property's annotation tells Gradle which bucket it's in. ### Input annotations - `@Input` — a serializable value property (`String`, `Int`, `Boolean`, `Enum`, `Property<T>` of those). Its value is hashed. - `@InputFile` / `@InputFiles` / `@InputDirectory` — file inputs. Content is hashed; how the path counts is governed by `@PathSensitive`. - `@Classpath` / `@CompileClasspath` — file collections hashed with classpath normalization (ignore order/timestamps; compile classpath is ABI-aware). - `@Nested` — the property is an object whose *own* annotated properties recurse into the key. Used for grouped/structured inputs. ### Output annotations - `@OutputFile` / `@OutputFiles` — single/multiple output files. - `@OutputDirectory` / `@OutputDirectories` — output directory trees. These define what the cache stores and restores. If an output isn't declared, Gradle won't store it — and the task can't be cached correctly. ## @Internal — "ignore this property" Gradle expects **every** property on a task to be classified. A property that affects neither the key nor the stored outputs gets `@Internal`: ```kotlin @get:Internal val logger: Logger get() = ... ``` Typical `@Internal` uses: loggers, injected services, computed convenience getters derived from other declared inputs, temporary scratch paths that don't affect outputs. An unclassified property produces a validation problem (`Property 'x' is not annotated...`). ## The danger of misusing @Internal Because `@Internal` removes a property from the cache key, marking something `@Internal` that genuinely influences the output means two different real inputs hash to the same key — a **false hit** restoring wrong outputs. The rule of thumb: if changing the value should change the result, it must be a declared `@Input*`, never `@Internal`. ## Optional and incremental modifiers - `@Optional` — the input may be absent (null) without failing validation. - `@SkipWhenEmpty` — if an input file collection is empty, skip the task (it produces no outputs). - `@Incremental` — pairs with `InputChanges` for incremental task actions; orthogonal to caching but commonly co-declared. ## Quick reference ```kotlin @CacheableTask abstract class Render : DefaultTask() { @get:Input abstract val theme: Property<String> @get:InputFiles @get:PathSensitive(PathSensitivity.RELATIVE) abstract val templates: ConfigurableFileCollection @get:Nested abstract val options: RenderOptions @get:OutputDirectory abstract val outDir: DirectoryProperty @get:Internal val clock: Clock get() = Clock.systemUTC() } ```
- What happens if you leave a task property with no input/output annotation at all?Gradle raises a validation problem ("property is not annotated with an input or output annotation"). You must classify it — usually @Internal if it doesn't affect the key, or the right @Input*/@Output* if it does.
- Why is marking a result-affecting property @Internal dangerous?@Internal removes the property from the cache key. If it actually influences the output, two different inputs hash to the same key and Gradle can restore the wrong outputs — a false cache hit / correctness bug.
saying these in an interview costs you the question
- Using @Internal to silence a validation warning on a property that does affect outputs.
- Forgetting @OutputDirectory/@OutputFile, leaving the cache nothing to store.
- Thinking @Nested flattens to a string rather than recursing into the nested object's own annotations.