Which annotations make a task's inputs and outputs participate in up-to-date checks, and what happens if you forget to declare one?
answer
- @Input / @InputFile / @InputFiles
- @OutputFile / @OutputDirectory
- @Internal = not tracked
- undeclared input → stale UP-TO-DATE
- validation warns on missing annotations
basics
~20 sUse @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 sA 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 linesabstract 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
Name the basic annotations (@Input, @InputFile, @OutputFile) and that they drive UP-TO-DATE.
Distinguish @Input vs @InputFile content hashing, explain @Internal, and the stale-build consequence of omissions.
Discuss validation tooling, the input→output purity contract, and how missing declarations break caching too.
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.