skip to content

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