skip to content

finalizedBy

Running a teardown or reporting task after another one, even when that task fails. Interviewers ask for concrete uses and for how it interacts with dependsOn and with build failure.

on this pageshow

questions

5

What does finalizedBy do in Gradle, and when would you use it?

level: juniorimportance: must knowfreq 55%

answer

  1. runs after, even on failure
  2. teardown / cleanup task
  3. stop server after integration test
  4. only runs if finalized task is scheduled
  5. skipped if finalized task never executes

basics

~10 s

finalizedBy 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 lines
kotlin
tasks.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

for a junior

Know that finalizedBy runs a cleanup task afterward, even if the main task fails — like a finally block.

for a middle

Explain the run-on-failure semantics and contrast with dependsOn; give the stop-server example.

for a senior

Discuss the 'only if scheduled / only if it executed' nuances and why finalizedBy is the correct teardown primitive.

for a principal

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.

context

open as a page

How does finalizedBy differ from dependsOn and from mustRunAfter/shouldRunAfter?

level: middleimportance: must knowfreq 50%

basics

~10 s

dependsOn pulls a task in to run before; mustRunAfter/shouldRunAfter only constrain ordering when both tasks are already in the graph. finalizedBy schedules a task to run after — even on failure — for cleanup.

open as a page

Explain exactly when a finalizer task runs and when it doesn't, including failure and skip cases.

level: middleimportance: should knowfreq 40%

basics

~20 s

A finalizer runs after its finalized task whenever that task actually starts executing — success or failure. If the finalized task never executes (e.g. a prerequisite failed, or it isn't in the graph), the finalizer does not run.

open as a page

Design a robust setup/teardown using finalizedBy for an integration-test task that starts an external resource. What pitfalls do you guard against?

level: seniorimportance: should knowfreq 33%

basics

~10 s

Have the test task dependsOn a setup task and finalizedBy a teardown task, so teardown runs even on test failure. Guard against teardown not running when setup fails, idempotent teardown, and configuration-cache compatibility.

open as a page

Can finalizedBy create a circular dependency, and how does the finalization direction interact with the task graph?

level: seniorimportance: nice to knowfreq 22%

basics

~10 s

Finalization is one-directional: requesting a finalizer never pulls in its finalized task. But combining finalizedBy with dependsOn carelessly can create a true cycle, which Gradle rejects with a circular-dependency error.

open as a page