skip to content

How does an incremental task using InputChanges differ from a normal task that just declares @InputDirectory/@OutputDirectory, and when is the extra complexity justified?

level: juniorimportance: should knowfreq 38%

answer

  1. normal task: skip or full re-run
  2. incremental: re-run but process only delta
  3. both need declared inputs/outputs
  4. worth it: expensive per-file + large set + small edits
  5. you take on REMOVED/fallback correctness

basics

~20 s

A 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 s

Both 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

for a junior

Contrast skip-or-full-rerun vs. process-only-the-delta and name an expensive-codegen use case.

for a middle

Explain that both build on declared inputs/outputs and that incrementality is opt-in finer granularity with added correctness duties.

for a senior

Weigh maintenance cost vs. speed and identify when the coarse check is preferable.

for a principal

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.

context