skip to content

Parallel Test Execution

maxParallelForks and forkEvery for spreading tests across multiple JVM processes, and sizing them against available cores. Interviewers ask because it is the cheapest test-suite speedup and a common source of new flakiness.

on this pageshow

questions

5

What does the `maxParallelForks` property on Gradle's `Test` task do, and how do you set it?

level: juniorimportance: must knowfreq 70%

answer

  1. concurrent forked test JVMs per Test task
  2. default = 1 (sequential)
  3. distributes test CLASSES, not methods
  4. half-cores heuristic
  5. not org.gradle.parallel

basics

~10 s

maxParallelForks sets how many test JVM processes Gradle runs at once for a single Test task. Set it in the test task config, e.g. tasks.test { maxParallelForks = 4 }. Default is 1.

solid answer

~40 s

Each `Test` task runs its test classes in forked JVM processes. By default Gradle uses a single fork, so all test classes run sequentially in one JVM. `maxParallelForks` raises the number of test JVMs Gradle may run concurrently for that task — Gradle distributes test classes across those forks, giving real speedup for CPU-bound or moderately I/O-bound suites. ```kotlin tasks.withType<Test>().configureEach { maxParallelForks = (Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(1) } ``` Key caveats: parallelism is **within** one Test task; tests in different classes can now run truly concurrently, so shared mutable state (static fields, fixed ports, shared DB schemas, temp files) becomes a hazard. It's distinct from `org.gradle.parallel` (which parallelises *projects/tasks*) and from JUnit 5's in-JVM parallelism. A common heuristic is half the available processors.

code

kotlin · 4 lines
kotlin
tasks.withType<Test>().configureEach {
    // run up to N test JVMs in parallel for this task
    maxParallelForks = (Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(1)
}

go deeper

for a junior

Know it sets the number of concurrent test JVMs and defaults to 1; be able to set it and mention the half-cores heuristic.

for a middle

Explain class-level granularity, the difference from org.gradle.parallel and --max-workers, and the shared-state hazards parallelism exposes.

for a senior

Discuss tuning trade-offs (memory/GC vs throughput), isolating flaky shared-state suites, and combining with engine-level parallelism.

for a principal

Frame fork tuning as part of CI capacity planning — agent core/memory budgets, reproducibility, and standardising the heuristic across many modules.

## What forking means When Gradle's `Test` task runs, it does not execute your tests inside the Gradle daemon. It launches separate **forked JVM processes** ("test workers") and feeds test classes to them over a socket. This isolation keeps test-polluted classloaders and static state out of the build process. By default a `Test` task uses exactly **one** fork, so even though your machine has many cores, the test classes run one after another in a single JVM. ## maxParallelForks `maxParallelForks` is an `int` property on the `Test` task that caps how many of those forked JVMs may run **at the same time** for that one task. Gradle then hands out test *classes* to whichever fork is free, so different test classes execute truly in parallel across OS processes. ```kotlin tasks.withType<Test>().configureEach { maxParallelForks = 4 } ``` A portable, machine-aware setting derives it from the CPU count: ```kotlin maxParallelForks = (Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(1) ``` ## Granularity is the class Gradle's distribution unit is the **test class**, not the test method. A single class with 500 slow methods still runs in one fork; parallelism only helps when you have many classes. (Method-level parallelism is a separate concern handled inside the engine, e.g. JUnit 5's `junit.jupiter.execution.parallel.enabled`.) ## What it is NOT - It is **not** `org.gradle.parallel=true` — that flag parallelises *tasks across projects*, letting `:moduleA:test` and `:moduleB:test` run together. `maxParallelForks` parallelises *within one* Test task. - It is **not** the same as `--max-workers`, the global cap on Gradle worker processes. `maxParallelForks` is bounded by `--max-workers`: if the global cap is lower, you won't get all the forks you asked for. - It is **not** JUnit/TestNG in-JVM parallelism. ## Hazards introduced by parallelism Running classes concurrently surfaces hidden coupling: shared static singletons, hard-coded ports, a single shared database schema, fixed temp-file paths, or order dependence between classes. Fix by making each fork self-contained (random ports, per-fork DB schema, `@TempDir`), or isolate the offenders into their own sequential Test task. ## Picking a value There's no universally correct number. Half the cores is a safe start because test JVMs also consume memory and may themselves spawn threads. Profile: too few forks underuses the machine; too many causes memory pressure, GC thrash, and context-switching that can make the suite *slower*.

  • Does maxParallelForks parallelise individual test methods?
    No. Gradle's distribution granularity is the test class. A single class runs entirely in one fork; method-level parallelism is the engine's job (e.g. JUnit 5's parallel execution config).
  • How does maxParallelForks relate to org.gradle.parallel?
    They're orthogonal. org.gradle.parallel runs *tasks across projects* concurrently; maxParallelForks runs multiple test JVMs *within a single Test task*. You can use both together.

saying these in an interview costs you the question

  • Claiming it parallelises test methods rather than test classes.
  • Confusing it with org.gradle.parallel or --max-workers.
  • Assuming higher is always faster, ignoring memory/GC overhead.

context

open as a page

After enabling parallel test execution with `maxParallelForks`, previously-green tests start failing intermittently. What's likely happening and how do you address it?

level: middleimportance: must knowfreq 48%

basics

~20 s

Parallel forks expose hidden shared state — fixed ports, a single shared DB/schema, common temp files, or order dependence between test classes. Make each fork self-contained: random ports, per-fork resources, @TempDir, and remove cross-class ordering assumptions.

open as a page

What does `forkEvery` control on the Test task, and when would you set it to a low value?

level: middleimportance: should knowfreq 50%

basics

~20 s

forkEvery sets how many test classes run in a single forked JVM before Gradle restarts that fork. Default is 0 (unlimited — never restart). A low value like 1 gives each class a fresh JVM, isolating leaked state.

open as a page

How would you tune `maxParallelForks` to make best use of a CI machine, and what trade-offs do you weigh?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Start from available processors (often half of them), then profile. Weigh CPU throughput against per-fork memory (heap × forks must fit RAM), GC overhead, and the --max-workers cap. Match the value to the actual CI machine, not your laptop.

open as a page

Explain the difference between `maxParallelForks` on a Test task and the `org.gradle.parallel` build flag. Do they interact?

level: seniorimportance: should knowfreq 40%

basics

~20 s

maxParallelForks parallelises test classes within one Test task across multiple JVMs. org.gradle.parallel=true parallelises tasks across projects (e.g. two modules' tests run together). Both draw on the same --max-workers pool, so they compete for that global worker budget.

open as a page