skip to content

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

level: middleimportance: should knowfreq 50%

answer

  1. classes per fork before JVM restart
  2. default 0 = unlimited (never recycle)
  3. forkEvery=1 → fresh JVM per class
  4. combats leaks / static-state pollution
  5. expensive: pays JVM startup each restart

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.

solid answer

~50 s

`forkEvery` is a `Long` property controlling how many test classes a single test JVM executes before it is discarded and a new one started. The default `0` means *no limit* — a fork lives for the whole task. Setting `forkEvery = 100` recycles the JVM every 100 classes; `forkEvery = 1` gives every class a brand-new JVM. ```kotlin tasks.test { forkEvery = 100 // recycle the JVM periodically } ``` Use a low value to combat **state leakage** between tests — static singletons, memory leaks, mutated system properties, or native resources that accumulate and cause flakiness or `OutOfMemoryError`. The trade-off is steep: every fresh JVM pays full startup + classloading cost, so `forkEvery = 1` on a large suite is dramatically slower. It's orthogonal to `maxParallelForks`: the former controls *fork lifetime/recycling*, the latter controls *concurrent fork count*. Reach for it as a diagnostic or a targeted fix for a leaky subset, not as a default.

code

kotlin · 4 lines
kotlin
tasks.withType<Test>().configureEach {
    maxParallelForks = 4   // concurrency
    forkEvery = 100        // recycle each fork every 100 classes
}

go deeper

for a junior

Know forkEvery recycles the test JVM after N classes and that 0 (default) means never recycle.

for a middle

Explain the leak/state-pollution motivation, the JVM-startup cost trade-off, and that it's independent of maxParallelForks.

for a senior

Use it as a diagnostic to localise shared-state/leak bugs, then drive a root-cause fix and restore speed; reason about combining it with concurrency.

for a principal

Set policy: forkEvery as a temporary mitigation, not a standard; track and pay down the underlying leaks so suites stay fast across the org.

## The property `forkEvery` answers: *how many test classes should one forked test JVM run before Gradle throws it away and starts a fresh one?* - `0` (default): unlimited — a fork is reused for as many classes as it gets handed for the lifetime of the task. - `N > 0`: after running `N` classes, the fork is shut down and a new JVM is launched for subsequent classes. - `1`: a brand-new JVM per test class — maximum isolation, maximum cost. ```kotlin tasks.withType<Test>().configureEach { forkEvery = 100 } ``` ## Why restart forks at all? A long-lived JVM accumulates state across the classes it runs: - **Static / singleton state** mutated by one class and observed by a later one. - **Memory leaks** — caches, classloaders, thread-locals that never get released, eventually causing `OutOfMemoryError` or GC thrash that slows everything down. - **Global mutations** — system properties (`System.setProperty`), default locale/timezone, security providers, JVM-wide config left dirty. - **Native / OS resources** — file handles, native libraries, open sockets that pile up. Lowering `forkEvery` periodically wipes the slate clean by replacing the process. `forkEvery = 1` fully isolates each class as if run alone. ## The cost Every new fork pays: process spawn, JVM warmup, classpath scan, framework bootstrap (Spring context, etc.). On a 1,000-class suite, `forkEvery = 1` can multiply wall-clock time many-fold. So it is a **scalpel**, not a default: use it to (a) prove a leak exists by seeing flakiness vanish, or (b) protect a known-leaky subset that you can't yet fix. ## Relationship to maxParallelForks These are independent knobs: | Property | Controls | |---|---| | `maxParallelForks` | how many forks run **concurrently** | | `forkEvery` | how many classes each fork runs before **recycling** | You can combine them: `maxParallelForks = 4` with `forkEvery = 50` means up to 4 JVMs at once, each recycled every 50 classes. ## Diagnostic pattern If a suite passes when classes run alone but fails when run together, set `forkEvery = 1`. If the failures disappear, you've localised a shared-state/leak problem — then fix the root cause (clean up in `@AfterAll`, avoid mutable statics) and raise `forkEvery` back up so the suite stays fast.

  • What's the default value of forkEvery and what does it mean?
    0, which means unlimited — the fork is never restarted and runs as many classes as it's handed for the whole task.
  • Why is forkEvery = 1 a bad permanent default?
    It pays full JVM startup + classloading + framework bootstrap cost for every single class, which can multiply suite runtime many-fold. Use it to diagnose leaks, then fix the root cause.
  • Is forkEvery the same as maxParallelForks?
    No. forkEvery controls a fork's lifetime (classes before recycle); maxParallelForks controls how many forks run concurrently. They're independent and combinable.

saying these in an interview costs you the question

  • Saying the default is 1 (it's 0 = unlimited).
  • Confusing forkEvery (fork recycling) with maxParallelForks (concurrency).
  • Recommending forkEvery = 1 as a standard setting without acknowledging the runtime cost.

context