skip to content

How do you run code after a Gradle build finishes, and what does gradle.buildFinished give you access to?

level: juniorimportance: must knowfreq 55%

answer

  1. fires after build, success or failure
  2. BuildResult.getFailure() null on success
  3. classic teardown/cleanup hook
  4. NOT configuration-cache safe
  5. register in init script or plugin

basics

~20 s

Register a callback with gradle.buildFinished { result -> ... }. It runs once the build ends and gives you a BuildResult with result.failure, so you can do cleanup or reporting whether the build passed or failed.

solid answer

~40 s

`gradle.buildFinished` is a lifecycle hook that fires once after the build completes — on success and on failure. The closure receives a `BuildResult` exposing `getFailure()` (the `Throwable` that ended the build, or null on success) and `getAction()` (e.g. "Build"). You typically register it in an init script or plugin to run cleanup, stop daemons you started, or emit a custom report. It runs even if a task failed, which makes it the classic place for teardown. ```kotlin gradle.buildFinished { if (it.failure != null) println("Build failed: ${it.failure?.message}") } ``` Note: `buildFinished` is **not** compatible with the configuration cache. Modern Gradle steers you to the Flow API (`FlowScope`/`FlowAction`) instead for new code.

code

kotlin · 7 lines
kotlin
gradle.buildFinished { result ->
    if (result.failure != null) {
        logger.error("Build failed", result.failure)
    } else {
        logger.lifecycle("Build succeeded")
    }
}

go deeper

for a junior

Know the closure form gradle.buildFinished { ... }, that it runs at the end, and that result.failure tells you if it failed.

for a middle

Explain BuildResult contents, where you'd register it (init script vs plugin), and that it fires on the failure path for teardown.

for a senior

Flag the configuration-cache incompatibility and that the Flow API is the replacement for new code.

for a principal

Frame org-wide build telemetry/reporting via init scripts and the migration policy away from buildFinished across many repos.

## What `buildFinished` is Gradle runs in phases: **initialization**, **configuration**, then **execution**. The `Gradle` object (available as `gradle` in build scripts, settings scripts, and init scripts) exposes lifecycle callbacks. `gradle.buildFinished(closure)` registers an action that runs **once**, after the entire build has finished — regardless of whether it succeeded or failed. ## The `BuildResult` you receive The callback is handed a `org.gradle.BuildResult`: - `getFailure(): Throwable?` — the exception that terminated the build, or `null` if it succeeded. - `getAction(): String` — what Gradle was doing (typically `"Build"`). - `getGradle(): Gradle` — back-reference to the build. This is why `buildFinished` is the canonical teardown hook: it fires on the failure path too, so resources you spun up (test fixtures, external processes, temp directories, a started database) get cleaned up. ## Where to register it - **Init script** (`~/.gradle/init.gradle(.kts)` or `--init-script`) — applies to every build; good for org-wide reporting. - **Settings script** or a **plugin** — scoped to one project tree. ## The big caveat: configuration cache `buildFinished` captures a closure that may reference live build model objects (the `Project`, `Task` graph, services). Those cannot be serialized, so **`buildFinished` is incompatible with the configuration cache** and Gradle emits a problem when it is used under `--configuration-cache`. For new code Gradle recommends the **Flow API** (`FlowScope.always { ... }` registering a `FlowAction`), which declares its inputs as serializable `Property` values and therefore survives the cache. ```kotlin // Legacy completion hook gradle.buildFinished { result -> val outcome = if (result.failure == null) "OK" else "FAILED" logger.lifecycle("Build outcome: $outcome") } ``` ## Related hooks `buildFinished` is the last of a family: `gradle.beforeProject`/`afterProject`, `gradle.projectsEvaluated`, `gradle.taskGraph.whenReady`. `buildFinished` is strictly the post-execution one.

  • Does the callback run if a task in the build fails?
    Yes. That is the point of it — it runs on both the success and failure paths, with result.failure populated on failure, so it is safe for teardown.
  • Why might buildFinished print a deprecation/problem warning in a modern build?
    Because it is incompatible with the configuration cache; Gradle reports it as a problem and recommends migrating to the Flow API.

saying these in an interview costs you the question

  • Saying it only runs on success — it runs on failure too.
  • Claiming buildFinished is configuration-cache compatible.
  • Confusing it with afterProject (which is per-project, during configuration).

context