How do you run code after a Gradle build finishes, and what does gradle.buildFinished give you access to?
answer
- fires after build, success or failure
- BuildResult.getFailure() null on success
- classic teardown/cleanup hook
- NOT configuration-cache safe
- register in init script or plugin
basics
~20 sRegister 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 linesgradle.buildFinished { result ->
if (result.failure != null) {
logger.error("Build failed", result.failure)
} else {
logger.lifecycle("Build succeeded")
}
}go deeper
Know the closure form gradle.buildFinished { ... }, that it runs at the end, and that result.failure tells you if it failed.
Explain BuildResult contents, where you'd register it (init script vs plugin), and that it fires on the failure path for teardown.
Flag the configuration-cache incompatibility and that the Flow API is the replacement for new code.
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).