What does finalizedBy do in Gradle, and when would you use it?
answer
- runs after, even on failure
- teardown / cleanup task
- stop server after integration test
- only runs if finalized task is scheduled
- skipped if finalized task never executes
basics
~10 sfinalizedBy declares that a finalizer task must run after a given task, even if that task fails. It's used for cleanup or teardown work, like releasing resources or generating a report after tests.
solid answer
~40 s`finalizedBy` registers a *finalizer* task that runs once the *finalized* task has executed — and crucially, it runs **even if the finalized task fails**. The classic use is teardown: stopping a server you started for integration tests, or generating a report regardless of test outcome. ```kotlin tasks.named("integrationTest") { finalizedBy("stopTestServer") } ``` The finalizer only runs if its finalized task is actually scheduled to run in this build. If `integrationTest` is excluded or up-to-date-skipped (still 'executed'), the finalizer still runs; if it's never in the task graph, the finalizer is not pulled in by the finalization relationship alone. It's the right tool whenever you must guarantee cleanup after success *or* failure.
code
kotlin · 7 linestasks.register("startTestServer") { doLast { println("server up") } }
tasks.register("stopTestServer") { doLast { println("server down") } }
tasks.named("integrationTest") {
dependsOn("startTestServer")
finalizedBy("stopTestServer") // runs even if tests fail
}go deeper
Know that finalizedBy runs a cleanup task afterward, even if the main task fails — like a finally block.
Explain the run-on-failure semantics and contrast with dependsOn; give the stop-server example.
Discuss the 'only if scheduled / only if it executed' nuances and why finalizedBy is the correct teardown primitive.
Frame it as the building block for reliable resource lifecycle in CI pipelines and shared convention plugins.
## What `finalizedBy` is Gradle builds a **directed acyclic graph (DAG)** of tasks before executing anything. Among the relationships you can declare between tasks, `finalizedBy` is the one designed for **cleanup/teardown semantics**: task B is a *finalizer* of task A, meaning whenever A runs, B is scheduled to run afterward — **including when A fails**. ```kotlin tasks.register("startServer") { doLast { /* start */ } } tasks.register("stopServer") { doLast { /* stop */ } } tasks.named("integrationTest") { dependsOn("startServer") finalizedBy("stopServer") } ``` Here `startServer` runs before the tests (a `dependsOn` relationship), and `stopServer` runs after — guaranteed — so the server is always torn down even if a test throws. ## Key behavioral rules 1. **Runs on failure.** This is the whole point. If the finalized task fails, the finalizer still runs. (The build still fails overall.) 2. **Only runs if the finalized task is scheduled.** The finalizer is pulled into the graph *because* its finalized task is in the graph. If you run only `stopServer` directly, fine; but `stopServer` won't drag `integrationTest` in — finalization points 'forward' only. 3. **Finalizer is skipped if the finalized task is skipped before executing.** Specifically, if the finalized task does not start executing (e.g. a `dependsOn` of it failed first, so it never ran), the finalizer is **not** run — there was nothing to finalize. This is a common gotcha. 4. **Ordering, not just timing.** `finalizedBy` also implies the finalizer runs *after* the finalized task, so you don't need a separate ordering rule. ## Why not just `dependsOn`? `dependsOn` makes the dependency run *before* and only when the dependent runs successfully along the way; it does not give you 'run-after-even-on-failure' cleanup. `finalizedBy` is purpose-built for the teardown case. (The inverse direction, `mustRunAfter`/`shouldRunAfter`, only constrains ordering when both tasks happen to be in the graph — it does not *pull* the other task in, and it does not run on failure.) ## Typical uses - Stop a database/web server started for integration tests. - Delete temporary directories or test fixtures. - Aggregate/publish a test report after a test task, success or fail. - Release a held lock or license. ## API surface `finalizedBy(...)` accepts task names, `Task` objects, `TaskProvider`s, or anything resolvable to a task — same coercion as `dependsOn`. There is also a `Task.getFinalizedBy()` collection you can manipulate.
- If integrationTest fails, does the overall build still fail even though the finalizer ran?Yes. The finalizer running does not mask the failure — the build exits non-zero because integrationTest failed; the finalizer just guarantees cleanup happened first.
- What is the difference between finalizedBy and dependsOn?dependsOn pulls a task in to run *before* and as a prerequisite; finalizedBy schedules a task to run *after*, including on failure, for cleanup. They're complementary, not interchangeable.
Like a finally block in try/finally: the cleanup runs whether the body succeeded or threw.
saying these in an interview costs you the question
- Saying the finalizer only runs on success — its defining feature is running on failure too.
- Claiming finalizedBy makes the build pass despite a failed finalized task — it does not change the build outcome.