How does an incremental task using InputChanges differ from a normal task that just declares @InputDirectory/@OutputDirectory, and when is the extra complexity justified?
answer
- normal task: skip or full re-run
- incremental: re-run but process only delta
- both need declared inputs/outputs
- worth it: expensive per-file + large set + small edits
- you take on REMOVED/fallback correctness
basics
~20 sA normal task with declared inputs/outputs is skipped entirely if nothing changed, but re-runs fully if anything changed. An incremental task re-runs but processes only the changed files. The complexity is justified when per-file work is expensive and only a few of many files change.
solid answer
~50 sBoth rely on Gradle declaring inputs and outputs for UP-TO-DATE checking, but they differ in granularity of *re-execution*. A normal task is binary: unchanged → `UP-TO-DATE`, skipped; changed → the whole `@TaskAction` runs again over all inputs. An incremental task adds an `InputChanges` parameter and `@Incremental` on a file input so that, when it does re-run, it can ask Gradle exactly which files were ADDED/MODIFIED/REMOVED and process only that delta. The extra code (delta handling, REMOVED cleanup, full-rebuild fallback) is only worth it when per-file processing is costly (codegen, transpilation, asset processing), the input set is large, and typical edits touch only a handful of files — so reprocessing the whole set on every change would dominate build time. For cheap tasks or ones where any change invalidates all output, the coarse UP-TO-DATE check is simpler and just as good.
go deeper
Contrast skip-or-full-rerun vs. process-only-the-delta and name an expensive-codegen use case.
Explain that both build on declared inputs/outputs and that incrementality is opt-in finer granularity with added correctness duties.
Weigh maintenance cost vs. speed and identify when the coarse check is preferable.
Set org guidance on where incrementality pays off and how it layers with the build cache for shared plugins.
## Same foundation, different granularity Gradle's incremental *build* is built on declared inputs/outputs. For any task you annotate inputs (`@InputFile`, `@InputDirectory`, `@Input`, `@Classpath`) and outputs (`@OutputFile`, `@OutputDirectory`). Gradle snapshots them; if the snapshot is unchanged from last time, the task is `UP-TO-DATE` and skipped. This is **task-level** incrementality and you get it for free. ### Normal task: all-or-nothing re-execution When *anything* in the snapshot changes, the entire `@TaskAction` runs. If your task transpiles 5,000 files and you edited one, all 5,000 are reprocessed. Correct, simple — but potentially slow. ### Incremental task: delta re-execution You opt into finer granularity: ```kotlin abstract class Transpile : DefaultTask() { @get:Incremental @get:InputDirectory abstract val src: DirectoryProperty @get:OutputDirectory abstract val out: DirectoryProperty @TaskAction fun run(changes: InputChanges) { changes.getFileChanges(src).forEach { c -> val o = out.file(c.normalizedPath).get().asFile if (c.changeType == ChangeType.REMOVED) o.delete() else transpile(c.file, o) } } } ``` Now editing one file reprocesses one file. The task still re-runs (it's not UP-TO-DATE — something changed), but the *work inside* is proportional to the change. ## The cost/benefit **Benefits:** dramatically less per-build CPU when edits are small relative to the input set. **Costs:** you now own correctness concerns Gradle handled for you — REMOVED cleanup, the non-incremental/all-ADDED fallback, deterministic output mapping, and the risk of cross-file dependency bugs. That's real, ongoing maintenance. ## When to use which - **Use the coarse check** when the task is cheap, the input set is small, or any change legitimately invalidates the whole output (e.g., concatenating all inputs into one file). - **Use InputChanges** when per-file work is expensive, the input set is large, files are largely independent, and typical edits are small. Code generators, transpilers, and asset pipelines are the classic fits. ## A note on layering Incremental tasks compose with the build cache and UP-TO-DATE: if nothing changed the task is still skipped entirely (you never enter the action), and if outputs are cacheable it can be restored remotely. InputChanges only matters on the runs that *do* execute.
- Does an incremental task still get skipped entirely when nothing changes?Yes. UP-TO-DATE still applies — if the input/output snapshot is unchanged the task is skipped and the action never runs. InputChanges only matters on runs that actually execute.
- Give a case where the coarse check is the better choice.A task that concatenates all inputs into a single output: any input change requires rebuilding the whole output, so per-file deltas add complexity with no benefit.
Normal UP-TO-DATE is a light switch: the room is fully lit or fully dark. InputChanges is a dimmer per bulb — you only flip the bulbs whose state actually changed.
saying these in an interview costs you the question
- Saying incremental tasks avoid re-running — they re-run but do less work.
- Adding InputChanges to a cheap task where it only adds correctness risk.
- Thinking you lose UP-TO-DATE skipping by going incremental.