skip to content

How does the Worker API surface failures from a WorkAction, and how should a plugin author handle errors across submitted units?

level: middleimportance: should knowfreq 30%

answer

  1. throw in execute -> WorkerExecutionException
  2. task fails
  3. multiple failures aggregated
  4. surfaces at await or task end
  5. processIsolation -> serialize exception

basics

~20 s

An exception thrown in a WorkAction.execute() is captured by Gradle and rethrown to the build as a WorkerExecutionException, failing the task. With multiple failing units, Gradle aggregates them. The failure surfaces at await() if you call it, otherwise at task end.

solid answer

~50 s

When a `WorkAction.execute()` throws, Gradle captures the exception and surfaces it to the build as a **`WorkerExecutionException`** that fails the owning task. If several submitted units fail, Gradle aggregates them (you see the multiple causes). The timing of the failure depends on whether you called `await()`: if you did, it rethrows at that point; if you didn't, Gradle propagates it at the **implicit end-of-task await**. For process isolation, the exception is serialized back from the worker JVM, so worker exceptions should be serializable for clean reporting. As an author, you generally **let it propagate** — don't swallow it — because a failed unit should fail the task. If you need partial-failure semantics (continue other units, collect failures), structure the work so each unit records its own result via outputs and check those after, rather than relying on exceptions; but the default and usually correct behavior is fail-fast at the barrier/task end.

code

kotlin · 7 lines
kotlin
abstract class ConvertAction : WorkAction<ConvertParams> {
    override fun execute() {
        val f = parameters.input.asFile.get()
        require(f.exists()) { "missing input: $f" }
        // throwing here fails the owning task via WorkerExecutionException
    }
}

go deeper

for a junior

Know a thrown exception in a WorkAction fails the task.

for a middle

Explain WorkerExecutionException, aggregation of multiple failures, and when it surfaces.

for a senior

Discuss process-isolation serialization of exceptions and structured partial-failure patterns.

for a principal

Set conventions for error reporting/serializable exception contracts across a plugin portfolio.

## How failures propagate A `WorkAction.execute()` is just code; if it throws, Gradle does not let that exception vanish into a worker thread/process. Instead: 1. Gradle **captures** the throwable from the unit. 2. It wraps/propagates it to the build as a **`WorkerExecutionException`** (a `WorkExecutionException`/`WorkerExecutionException` type) that **fails the owning task**. 3. If **multiple** submitted units fail, Gradle **aggregates** them so the report includes each cause rather than only the first. ## Where the failure surfaces - If the action calls **`await()`**, the exception is rethrown at the `await()` call. - If it doesn't, Gradle's **implicit end-of-task await** propagates it when the task finishes. Either way the task fails — `await()` only changes *when* you observe it. ## Process isolation specifics Under `processIsolation`, the action runs in a separate JVM, so its exception must be **serialized** back to the build JVM. Custom exception types used in workers should be serializable (and their classes available on the build side) for the cause chain to reconstruct cleanly; otherwise Gradle may report a generic wrapper. ## Author guidance ```kotlin abstract class ConvertAction : WorkAction<ConvertParams> { override fun execute() { val f = parameters.input.asFile.get() if (!f.exists()) error("missing input: $f") // becomes a WorkerExecutionException // ... convert } } ``` - **Don't swallow** exceptions just to make the build green — a failed unit means failed work. - **Fail-fast is the default.** All independent units still get submitted, but the task ends in failure with aggregated causes. - For **partial-failure / collect-all** semantics, don't throw; have each unit write a result (success/failure record) to an output, then after the queue drains inspect those outputs and decide. This keeps reporting deterministic and avoids racing exceptions. - Keep worker exception types **serializable** when using process isolation. ## Summary Worker failures are first-class: captured, aggregated, and turned into task failure via `WorkerExecutionException`. Let them propagate by default; opt into structured result-collection only when you specifically need partial success.

  • If three out of ten units throw, what does the build report?
    Gradle aggregates the failures into a WorkerExecutionException with multiple causes, so all three failures appear, and the task fails.
  • How would you implement 'process all units, then report which failed' instead of fail-fast?
    Don't throw in execute(). Have each unit write a success/failure record to an output file/property, let the queue drain, then after submission inspect the records and decide whether to fail or summarize.

saying these in an interview costs you the question

  • Catching and ignoring exceptions inside execute() to keep the build green.
  • Assuming only the first failing unit is reported — Gradle aggregates multiple.
  • Using non-serializable custom exceptions with processIsolation and expecting a clean cause chain.

context