How do you run a Gradle build asynchronously with the Tooling API, and how do you receive its success or failure?
answer
- run(ResultHandler) returns immediately
- onComplete(Void) / onFailure(GradleConnectionException)
- callbacks on background thread
- cancellation -> onFailure (BuildCancelledException)
- don't block the UI thread
basics
~10 sCall run(ResultHandler) instead of run(). It returns immediately and invokes onComplete(Void) on success or onFailure(GradleConnectionException) on failure, on a background thread.
solid answer
~40 s`BuildLauncher.run()` is synchronous and blocks the caller. For non-blocking execution you pass a `ResultHandler<Void>` to `run(ResultHandler)`: the call returns immediately and the build runs on a Tooling API background thread. Results arrive via two callbacks — `onComplete(Void result)` on success, and `onFailure(GradleConnectionException failure)` on failure (build failures, connection problems, or cancellation all surface here). This matters in IDEs and UIs: you must not block the event-dispatch thread for the duration of a build, so you launch asynchronously and update the UI from the callbacks (marshalling back onto the UI thread yourself). The same pattern applies to other long-running operations (`ModelBuilder`, `BuildActionExecuter`). Because callbacks run off your thread, any shared state you touch must be thread-safe.
code
kotlin · 10 linesconnection.newBuild()
.forTasks("build")
.run(object : ResultHandler<Void> {
override fun onComplete(result: Void?) { ui.markSuccess() }
override fun onFailure(failure: GradleConnectionException) {
if (failure is BuildCancelledException) ui.markCancelled()
else ui.markFailure(failure)
}
})
// returns immediately; build runs on a background threadgo deeper
Know that run(ResultHandler) is the non-blocking variant with onComplete/onFailure callbacks.
Explain the threading model, the Void result, and that cancellation/failure both land in onFailure.
Discuss safe UI marshalling, thread-safe shared state, and unifying this with model/action async execution.
Design a concurrency model for many parallel builds: bounded executors, backpressure, cancellation propagation, and avoiding daemon exhaustion.
## Synchronous vs asynchronous Every long-running Tooling API operation — `BuildLauncher`, `ModelBuilder`, `BuildActionExecuter`, `TestLauncher` — exposes two execution styles: - A **blocking** form (`run()` / `get()`) that returns the result or throws. - A **non-blocking** form that takes a `ResultHandler<T>` and returns immediately. For `BuildLauncher` the result type is `Void`, so you implement `ResultHandler<Void>`. ## The ResultHandler contract ``` public interface ResultHandler<T> { void onComplete(T result); void onFailure(GradleConnectionException failure); } ``` - `onComplete(Void)` fires when the build succeeds. The value is `null` for a build (it carries no model). - `onFailure(GradleConnectionException)` fires for **any** failure: a failed build, a broken daemon connection, an unsupported version, or a **cancellation** (which arrives as a `BuildCancelledException`, a subtype). You inspect the exception to decide how to report it. Exactly one of the two callbacks is invoked, once. ## Why async matters in IDEs A Gradle build can take minutes. If you call `run()` on a UI thread you freeze the application. The async form lets you: 1. Kick off the build and return control to the UI immediately. 2. Show progress and allow cancellation while it runs. 3. React in `onComplete`/`onFailure` — but remember these callbacks execute on a **Tooling API thread**, not your UI thread, so you must re-dispatch UI updates onto the UI thread (e.g. `SwingUtilities.invokeLater`). ## Threading caution Because the callback runs off your calling thread, any mutable state shared between the launching code and the handler must be safely published / thread-safe. Don't assume happens-before guarantees beyond what the JMM gives you. ``` launcher.forTasks("build").run(new ResultHandler<Void>() { public void onComplete(Void unused) { ui.markSuccess(); } public void onFailure(GradleConnectionException e) { ui.markFailure(e); } }); // returns immediately; build runs in background ```
- On which thread do onComplete/onFailure run?On a Tooling API background thread, not your calling/UI thread — so UI updates must be marshalled back onto the UI thread, and shared state must be thread-safe.
- How does a cancellation surface in the async API?Through onFailure, as a BuildCancelledException (a subtype of GradleConnectionException), not via onComplete.
- What is the result type passed to onComplete for a BuildLauncher?Void (effectively null) — a build produces no model; ModelBuilder by contrast delivers the requested model object.
Like submitting a Future with a completion callback instead of calling .get() and waiting — you hand off the work and get notified when it lands.
saying these in an interview costs you the question
- Updating UI components directly inside onComplete/onFailure without re-dispatching to the UI thread.
- Expecting cancellation to call onComplete rather than onFailure.
- Assuming run(ResultHandler) blocks like run() does.