skip to content

Which input/output annotations does Gradle use to build a cacheable task's key, and what does @Internal mean for caching?

level: middleimportance: should knowfreq 40%

answer

  1. @Input scalar, @InputFiles content
  2. @Classpath normalized, @Nested recurses
  3. @OutputFile/@OutputDirectory stored
  4. @Internal = ignored for key
  5. 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 s

Gradle 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
kotlin
@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

for a junior

Recognize @Input/@InputFiles/@OutputFile and that @Internal means 'ignore for caching'.

for a middle

Map each property to the correct annotation, know @Nested recursion and the unannotated-property warning.

for a senior

Reason about @Internal misuse causing false hits and about @Optional/@SkipWhenEmpty interactions.

for a principal

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.

context