skip to content

Worker Isolation Modes & Forking

Choosing between no isolation, classloader isolation, and process isolation for workers, and the fork options and heap settings each implies. Asked because the choice trades startup cost against dependency conflicts.

on this pageshow

questions

5

The Gradle Worker API offers three isolation modes — noIsolation, classLoaderIsolation, and processIsolation. What does each one isolate, and how do they differ?

level: middleimportance: must knowfreq 55%

answer

  1. noIsolation = daemon JVM, buildscript classpath
  2. classLoaderIsolation = daemon JVM, separate classloader
  3. processIsolation = forked JVM + forkOptions
  4. cheapest-that-is-safe
  5. cost rises: nothing → classloader → fork

basics

~10 s

noIsolation runs work in the Gradle JVM with the buildscript classpath. classLoaderIsolation runs it in the same JVM but under a separate classloader from a custom classpath. processIsolation runs it in a forked JVM.

solid answer

~40 s

When you submit work via `WorkerExecutor`, you pick one of three `WorkerSpec` factories. **noIsolation** is the lightest: the work runs in the Gradle daemon's JVM using the plugin/buildscript classloader — no isolation, fastest, but the action can see and mutate daemon state. **classLoaderIsolation** still runs in the daemon JVM but loads your `WorkAction` (and its `classpath`) in an isolated classloader, so you can pin a different version of a library than Gradle itself uses without conflicts. **processIsolation** forks a brand-new worker JVM; the action runs there with its own classpath and configurable `forkOptions` (heap, JVM args, system properties). Each step up costs more (classloader setup, or a full JVM fork) but buys stronger isolation. Pick the cheapest mode that still keeps your work safe.

code

kotlin · 8 lines
kotlin
val queue = workerExecutor.processIsolation {
    classpath.from(configurations["myTool"])
    forkOptions {
        maxHeapSize = "2g"
        jvmArgs("-XX:+HeapDumpOnOutOfMemoryError")
    }
}
queue.submit(MyWork::class.java) { /* params */ }

go deeper

for a junior

Name the three modes and state plainly that they go from no isolation, to separate classloader, to separate JVM.

for a middle

Explain what each isolates (nothing / classes / process), the relative cost, and that you pick the cheapest safe mode.

for a senior

Discuss the trade-offs concretely: version clashes → classLoaderIsolation, heap/JVM-args/System.exit safety → processIsolation, and worker-daemon reuse mitigating fork cost.

for a principal

Frame it as a policy for a shared plugin: defaulting heavy tooling to processIsolation for daemon stability, and standardizing classpath/heap conventions across teams.

## The problem isolation modes solve Gradle's Worker API lets a task break its work into units that run in parallel and benefit from build caching. But "where" that work runs matters: running arbitrary code inside the long-lived Gradle daemon JVM is risky if the code is heavy, leaks memory, uses `System.exit`, or needs a library version that clashes with Gradle's own dependencies. **Isolation modes** let you trade performance for safety. When you submit work you call one of three methods on `WorkerExecutor`, each returning a `WorkQueue` configured with a spec: - `noIsolation()` → `WorkerSpec` - `classLoaderIsolation(Action<ClassLoaderWorkerSpec>)` → lets you set `classpath` - `processIsolation(Action<ProcessWorkerSpec>)` → lets you set `classpath` and `forkOptions` ## noIsolation The `WorkAction` runs **in the Gradle daemon JVM, on the buildscript classloader**. Nothing is isolated. This is the fastest mode — no classloader construction, no fork — and is correct when your action is well-behaved and only depends on classes already on the plugin classpath. The danger: the action shares static state and the classloader with Gradle, so a leaked thread, a mutated singleton, or a version clash can corrupt the daemon. ## classLoaderIsolation The action still runs **in the daemon JVM**, but Gradle creates a **dedicated classloader** for it from the `classpath` you provide (via `ClassLoaderWorkerSpec.getClasspath()`). This isolates *classes*, not *memory or the process*: you can run a tool that needs, say, an older Guava without it colliding with the version Gradle uses internally. It is more expensive than noIsolation (classloader creation, and the action's classes are re-loaded) but far cheaper than a fork. It does **not** protect against `System.exit`, native crashes, or per-worker heap tuning. ## processIsolation Gradle **forks a separate worker JVM**. The action runs there with its own `classpath` and a `JavaForkOptions` you configure through `ProcessWorkerSpec.forkOptions { ... }` — `maxHeapSize`, `minHeapSize`, `jvmArgs`, `systemProperty`, `bootstrapClasspath`, environment, etc. This is the strongest and most expensive mode: a full JVM start (mitigated because Gradle reuses idle worker daemons across invocations). Use it when work is memory-hungry and needs its own heap, may call `System.exit`, may crash natively, or must be fully sandboxed from the build JVM. ## Choosing Rule of thumb: pick the **cheapest mode that is still safe**. Start with noIsolation; move to classLoaderIsolation when you hit a dependency/version clash; move to processIsolation when you need a separate heap, JVM args, or hard process-level isolation. ```kotlin abstract class MyTask : DefaultTask() { @get:Inject abstract val executor: WorkerExecutor @TaskAction fun run() { // cheapest val q1 = executor.noIsolation() // isolate classes val q2 = executor.classLoaderIsolation { classpath.from(myToolClasspath) } // isolate the whole process + tune heap val q3 = executor.processIsolation { classpath.from(myToolClasspath) forkOptions { maxHeapSize = "1g"; jvmArgs("-XX:+UseG1GC") } } } } ```

  • Which mode is the default if you just want parallelism with no special needs?
    There is no implicit default — you must call one of the three factory methods. The natural starting choice is noIsolation() because it has the least overhead; you move up only when isolation requires it.
  • Does classLoaderIsolation fork a JVM?
    No. It runs in the same Gradle daemon JVM; only the classloader is separate. Only processIsolation forks a new JVM.

saying these in an interview costs you the question

  • Saying classLoaderIsolation runs in a separate process — it does not, only the classloader differs.
  • Claiming noIsolation gives you a fresh classpath you can customize — it uses the existing buildscript classpath.

context

open as a page

When using processIsolation, how do you configure the forked worker JVM — for example its heap size and JVM arguments — and what API exposes those settings?

level: middleimportance: should knowfreq 40%

basics

~10 s

Call workerExecutor.processIsolation { forkOptions { ... } }. forkOptions is a JavaForkOptions where you set maxHeapSize, minHeapSize, jvmArgs, systemProperty, environment, and the working directory of the forked JVM.

open as a page

Why does noIsolation use the buildscript classpath, and what risk does that create when your worker action depends on a library Gradle also ships?

level: middleimportance: should knowfreq 30%

basics

~20 s

noIsolation runs the action on the plugin/buildscript classloader, so you cannot pick a custom classpath. If your action needs a different version of a library Gradle bundles, you get the version Gradle exposes — leading to conflicts or wrong behavior.

open as a page

Why must WorkAction parameters be isolatable/serializable, and how does the requirement change across the three isolation modes?

level: seniorimportance: should knowfreq 25%

basics

~20 s

Gradle snapshots (isolates) work parameters when you submit, so each unit gets an independent copy. For processIsolation the parameters must cross to a forked JVM, so they must be serializable. Use Property/managed types, not arbitrary mutable objects.

open as a page

You are deciding between classLoaderIsolation and processIsolation for a worker-based task. What concrete factors push you from one to the other?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Use classLoaderIsolation when you only need a separate classpath (version isolation) and the work is well-behaved. Move to processIsolation when work needs its own heap/JVM args, may call System.exit, may crash natively, or must not touch daemon memory.

open as a page