How does --max-workers interact with a Test task's maxParallelForks? If I set maxParallelForks=8 but --max-workers=4, what happens?
answer
- global ceiling vs per-task request
- forks consume leases
- min(maxParallelForks, available leases)
- smaller constraint wins
- each fork = a JVM heap
basics
~10 smax-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.
solid answer
~40 s`maxParallelForks` on a `Test` task is a *request* for how many test JVMs that task may run in parallel. But every running fork must hold a worker lease, and there are only `max-workers` leases for the whole build. So `max-workers` is the hard ceiling. With `maxParallelForks=8` and `max-workers=4`, you'll never see more than 4 forks running simultaneously — and if any other work is going on concurrently (other tasks, other test tasks), they all compete for those same 4 leases. The practical rule: size `max-workers` first as your global budget, then set `maxParallelForks` per test task to a value that makes sense locally; the smaller effective number wins. Setting `maxParallelForks` high while keeping `max-workers` low is a common mistake that silently leaves test parallelism on the table.
code
kotlin · 7 lines// build.gradle.kts
tasks.withType<Test>().configureEach {
// request up to N forks for THIS task...
maxParallelForks = (Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(1)
}
// ...but org.gradle.workers.max in gradle.properties is the hard ceiling
// for the whole build. The smaller binding number wins.go deeper
Know maxParallelForks defaults to 1 and that max-workers is a global cap.
Explain that forks consume leases and the smaller of (maxParallelForks, max-workers) binds; give the arithmetic.
Discuss tuning both together, the memory ceiling, and contention when multiple tasks run concurrently.
Define org-wide conventions for fork/worker sizing on shared CI runners to avoid noisy-neighbor OOMs.
## Two different knobs - **`max-workers`** — global ceiling on *all* concurrent work in the build (one number for the whole invocation). - **`maxParallelForks`** — a property on each `Test` task saying how many JVM forks *that one task* may run at once. Defaults to `1`. They are not alternatives; they compose. Forks are work items, and work items need leases. ## The arithmetic Effective parallelism for a test task ≈ `min(maxParallelForks, leases currently available)`, and total leases never exceed `max-workers`. So: ``` maxParallelForks = 8, max-workers = 4 -> at most 4 forks at once maxParallelForks = 2, max-workers = 8 -> at most 2 forks (the task itself caps it) ``` The **smaller binding constraint wins**. If other tasks are also executing concurrently (parallel project execution, a second test task), they share the same 4 leases, so your test forks may get even fewer than 4. ## Common tuning idiom A frequent pattern is to derive forks from available processors: ```kotlin tasks.withType<Test>().configureEach { maxParallelForks = (Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(1) } ``` This keeps per-task forks reasonable while `max-workers` (default = processor count) provides the global cap. Note the memory implication: each fork is a JVM with its own heap, so 8 forks × a large `-Xmx` can OOM the machine even if `max-workers` allows it CPU-wise. ## Why it matters in interviews The insight being tested is that **Gradle has one global concurrency budget** and local knobs can only request *up to* it, never beyond. Candidates who think `maxParallelForks=8` guarantees 8 forks regardless of `max-workers` have the model wrong.
- Why might raising maxParallelForks not improve test wall-time at all?If max-workers is already the binding constraint, or other concurrent tasks are consuming the leases, the extra requested forks can't acquire leases. You'd need to raise max-workers too — and have enough memory.
- What resource, besides CPU, usually limits how high you can push forks?Memory. Each fork is a separate JVM with its own heap, metaspace, and GC. Aggressive parallel forks commonly OOM before they saturate CPU.
saying these in an interview costs you the question
- Claiming maxParallelForks overrides or ignores max-workers.
- Forgetting that forks share the global budget with all other concurrent tasks.
- Ignoring memory as the real ceiling when tuning forks.