skip to content

Which annotations make a task's inputs and outputs participate in up-to-date checks, and what happens if you forget to declare one?

level: middleimportance: must knowfreq 65%

answer

  1. @Input / @InputFile / @InputFiles
  2. @OutputFile / @OutputDirectory
  3. @Internal = not tracked
  4. undeclared input → stale UP-TO-DATE
  5. validation warns on missing annotations

basics

~20 s

Use @Input for values and @InputFile/@InputFiles for file inputs, and @OutputFile/@OutputDirectory for outputs. If you forget one, Gradle can't see it, so the task may show UP-TO-DATE even though that thing changed — a stale build.

solid answer

~40 s

A task property only affects up-to-date checking if it carries an input or output annotation. `@Input` captures a serializable value; `@InputFile`, `@InputFiles`, `@InputDirectory` capture file *content* fingerprints; `@OutputFile`, `@OutputFiles`, `@OutputDirectory` register produced files. Gradle snapshots exactly these and nothing else. If you read a file or system value the task depends on but never declare it, Gradle has no record of it: when that undeclared input changes, the input fingerprint is unchanged, the task is wrongly reported `UP-TO-DATE`, and the build is stale. Conversely, declaring an undeclared *output* matters because Gradle verifies outputs still exist; an unregistered output won't trigger re-execution if deleted. The fix is to declare every real input and output. Optional inputs use `@Optional`; values that shouldn't affect output but are needed for execution use `@Internal`.

code

kotlin · 9 lines
kotlin
abstract class StampTask : DefaultTask() {
    @get:Input abstract val version: Property<String>
    @get:InputFile abstract val template: RegularFileProperty
    @get:Internal abstract val scratchDir: DirectoryProperty   // needed but must not affect up-to-date
    @get:OutputFile abstract val output: RegularFileProperty

    @TaskAction
    fun stamp() { /* render template + version -> output */ }
}

go deeper

for a junior

Name the basic annotations (@Input, @InputFile, @OutputFile) and that they drive UP-TO-DATE.

for a middle

Distinguish @Input vs @InputFile content hashing, explain @Internal, and the stale-build consequence of omissions.

for a senior

Discuss validation tooling, the input→output purity contract, and how missing declarations break caching too.

for a principal

Position correct declarations as a build-correctness governance issue; enforce via the validation plugin in CI across all custom tasks.

## The annotation contract Up-to-date checking is only as accurate as the task's declarations. Gradle fingerprints **exactly** the properties you annotate: ### Input annotations - `@Input` — a value (String, number, boolean, enum, or `Serializable`). Captured by value. - `@InputFile` — a single file; its **content hash** is part of the fingerprint (path handling depends on path sensitivity, a separate concern). - `@InputFiles` / `@InputDirectory` — a collection / a directory tree of inputs, fingerprinted by content. - `@Classpath` / `@CompileClasspath` — classpath inputs with order/normalization semantics (sibling topic). ### Output annotations - `@OutputFile`, `@OutputFiles`, `@OutputDirectory`, `@OutputDirectories` — register where the task writes. Gradle both snapshots them after execution and checks they still exist/are unmodified before deciding UP-TO-DATE. ### Non-tracked helpers - `@Internal` — a property the task needs at runtime but that must **not** influence up-to-date checking. - `@Optional` — the annotated input/output may be absent without error. ## What "forgetting" costs you Consider a task that reads a config file but the property is plain (no annotation) or `@Internal`: ```kotlin abstract class GenerateTask : DefaultTask() { @get:Internal abstract val configFile: RegularFileProperty // BUG: should be @InputFile @get:OutputFile abstract val out: RegularFileProperty @TaskAction fun run() { /* reads configFile, writes out */ } } ``` Change `configFile`'s contents and re-run: its hash is not in the fingerprint, so Gradle sees no change and prints `UP-TO-DATE`. The output is now **stale**. This class of bug is silent and dangerous because the build "succeeds." The mirror problem: forgetting `@OutputFile` means Gradle won't notice if the output is deleted, and won't be able to cache it. ## Validation help Gradle runs **task validation**: missing or conflicting annotations (e.g., a file property with no input/output annotation) surface as validation warnings/problems, and with the validation plugin they can fail the build. The guidance: every property is either a declared input, a declared output, or explicitly `@Internal`. ## Why this is the heart of incremental builds The up-to-date decision is a pure function of declared inputs → expected outputs. Correct declarations make the decision sound; missing ones make it unsound. Reliability of every downstream feature (incremental builds, build cache, configuration cache reasoning about work) rests on this contract.

  • When should a property be @Internal instead of @Input?
    When the task needs the value at runtime but it must not influence whether the task is up to date — e.g., a temp directory, a logger, or a derived convenience accessor that doesn't change the output.
  • How does Gradle help you catch a missing annotation?
    Task input/output validation reports properties that lack a recognized annotation as validation problems; the validation plugin can escalate these to build failures.
  • What's the difference between @Input and @InputFile for a File property?
    @InputFile fingerprints the file's contents; @Input on a File would fingerprint only the path string, missing content changes — so file inputs must use @InputFile/@InputFiles.

saying these in an interview costs you the question

  • Putting @Input on a File expecting content tracking (it only captures the path).
  • Marking real inputs @Internal to 'silence warnings', which causes silent stale builds.
  • Assuming undeclared reads are tracked automatically — Gradle only sees what you annotate.

context