skip to content

Worker Count And Worker API

Bounding total concurrency with --max-workers or org.gradle.workers.max, and how that limit interacts with parallel projects and forked test JVMs. Asked because over-subscribing a CI agent makes builds slower, not faster.

on this pageshow

questions

5

What does Gradle's --max-workers flag (or org.gradle.workers.max property) control, and what is its default value?

level: juniorimportance: must knowfreq 55%

answer

  1. global concurrency ceiling
  2. shared by parallel tasks, test forks, Worker API
  3. default = available processors
  4. org.gradle.workers.max / --max-workers
  5. ceiling not target

basics

~10 s

It caps the maximum number of work items Gradle runs concurrently across the whole build — parallel tasks, test forks, Worker API jobs. It defaults to the number of CPU processors Gradle detects.

solid answer

~40 s

`--max-workers` (or `org.gradle.workers.max` in `gradle.properties`) sets the upper bound on how many units of work Gradle executes at once across the *entire* build. That global budget is shared by every concurrency mechanism: parallel project/task execution, forked test JVMs (`maxParallelForks`), and Worker API work items. By default Gradle sets it to the number of available processors reported by the JVM (`Runtime.availableProcessors()`). You lower it to leave headroom for other processes (e.g. on CI, or a laptop where you still want a responsive IDE), or raise/lower it to tune throughput. It is a ceiling, not a target — Gradle won't exceed it, but won't manufacture work to hit it either.

code

properties · 5 lines
properties
# gradle.properties
org.gradle.workers.max=4

# overridden per run:
# ./gradlew build --max-workers=6

go deeper

for a junior

Know that it caps concurrency and defaults to CPU count; know both the flag and the property name.

for a middle

Explain that the budget is shared across parallel tasks, test forks, and Worker API, and that it's a ceiling not a target.

for a senior

Discuss tuning trade-offs (memory vs CPU bound), CI container core-detection gotchas, and interaction with maxParallelForks.

for a principal

Frame org-wide defaults: standardizing workers.max in shared gradle.properties / init scripts across teams and CI to get predictable resource use.

## What the worker budget is Gradle has a single, build-wide **worker lease** pool. Every concurrent unit of work must acquire a lease before it runs, and there are only `max-workers` leases. This is the master throttle for *all* concurrency in a build, no matter where it comes from: - **Parallel project execution** (`org.gradle.parallel=true`) — tasks from independent projects running at the same time. - **Test forks** — JVMs spawned by the `Test` task; `maxParallelForks` requests several, but each running fork still consumes a lease. - **Worker API** work items submitted via `WorkerExecutor` (the *authoring* side is a separate topic; what matters here is that those items draw from the same budget). ## The default If you set nothing, Gradle picks `max-workers = Runtime.getRuntime().availableProcessors()` — the processor count the JVM sees. On an 8-core machine that's 8. Note that in containers this can be wrong if the JVM doesn't honor cgroup limits, which is a classic CI gotcha: the JVM may report the host's core count, not the container's quota. ## How to set it ```properties # gradle.properties org.gradle.workers.max=4 ``` or per-invocation: ```bash ./gradlew build --max-workers=4 ``` The command-line flag overrides the property for that invocation. ## Why tune it - **Lower** it to leave CPU/RAM for other work (IDE, browser, sibling CI jobs sharing a runner), or when builds thrash memory. - It interacts with **memory**: more workers = more concurrent JVMs/heap, so the right number is often memory-bound, not CPU-bound. - Setting it to `1` effectively serializes the build — useful for reproducing ordering bugs or for memory-starved environments. ## What it is NOT It is a *ceiling*. Gradle never spawns busy-work to reach it. If your task graph has no parallelism available (e.g. a single linear chain, or `org.gradle.parallel` is off so cross-project work can't overlap), a high `max-workers` does nothing.

  • If I have a single linear chain of tasks, does raising --max-workers speed it up?
    No. There is no independent work to overlap, so the extra leases sit idle. max-workers only helps when the task graph actually has parallelizable work.
  • Does --max-workers require org.gradle.parallel=true?
    Not for all concurrency. Test forks and Worker API items run concurrently within a project even without parallel project execution. But parallel execution *across* projects does require org.gradle.parallel=true; max-workers then caps that cross-project concurrency.

Think of it as the number of checkout lanes open in a store: you can never have more shoppers checking out at once than lanes, but opening more lanes doesn't create shoppers.

saying these in an interview costs you the question

  • Saying max-workers is the same flag as org.gradle.parallel — they're distinct; one is a ceiling, the other enables cross-project overlap.
  • Claiming the default is a fixed number like 4 rather than the detected processor count.

context

open as a page

How does --max-workers interact with a Test task's maxParallelForks? If I set maxParallelForks=8 but --max-workers=4, what happens?

level: middleimportance: must knowfreq 45%

basics

~10 s

max-workers is the global ceiling. maxParallelForks asks for up to 8 test JVMs, but each fork needs a worker lease, so with max-workers=4 you get at most 4 running forks at a time regardless.

open as a page

A colleague set org.gradle.workers.max=8 expecting the build to run modules in parallel, but it still runs them one at a time. Why, and what did they confuse it with?

level: middleimportance: should knowfreq 35%

basics

~10 s

max-workers only sets a ceiling; it doesn't enable cross-project parallel execution. For that you need org.gradle.parallel=true. They confused the budget cap with the parallel-execution switch.

open as a page

On CI, you set nothing for max-workers and builds intermittently OOM or thrash. How could core/processor detection inside a container cause this, and how do you fix it?

level: seniorimportance: should knowfreq 30%

basics

~10 s

If the JVM reads the host's core count instead of the container's cgroup quota, Gradle defaults max-workers too high and over-parallelizes — too many forks/heaps for the container's real memory. Fix: pin org.gradle.workers.max explicitly.

open as a page

How would you choose a value for org.gradle.workers.max for a memory-heavy multi-module build, and how do you validate the choice empirically?

level: seniorimportance: nice to knowfreq 20%

basics

~20 s

Start near the core count, but cap it so workers × per-fork/daemon heap fits available RAM. Then sweep a few values (e.g. 2/4/6/8), measure wall-time and GC/OOM with build scans, and keep the smallest value that gives near-peak speed.

open as a page