## TL;DR
The forked test JVM crashed or exited abnormally: out of memory, a System.exit() somewhere in the code or a dependency, or a native segfault. Surefire reports the death but not the cause, so the fix is detective work, not a config tweak. Check the fork's heap settings first, since CI runners have less memory than developer machines, then scan for System.exit calls, then rerun with a single fork and debug output to capture whatever killed the VM.

## The query
```text
maven surefire The forked VM terminated without properly saying goodbye in CI  -  how to fix
```

## Use this when
- the build fails with The forked VM terminated without properly saying goodbye
- the failure happens in CI but the same tests pass locally
- surefire reports exist but contain no test summary

## Not for
- tests fail with normal assertion errors (those need no fork debugging)
- compilation fails before surefire runs
- the fork dies on every machine including local (still use these steps, but it is not CI-specific)

## Steps

### 1. Open the surefire reports for the fork that died
The reports directory tells you which fork died and what it was running. Start there before touching any configuration.
```bash
ls target/surefire-reports/
grep -l "terminated" target/surefire-reports/*.txt
```
Expected output: you know which fork died and which test class it was executing.

### 2. Check the fork's heap settings
A fork with a tiny heap dies on the first large test. Look at the surefire argLine in the pom and compare it against the CI runner's available memory.
```bash
grep -A3 "argLine" pom.xml
free -m
```
Expected output: you see the configured -Xmx next to the runner's actual RAM, and whether the heap is undersized.

### 3. Search for System.exit and native crash signatures
Any System.exit() in main code, test code, or a dependency kills the fork instantly. Also check the CI log for hs_err files or segfault mentions.
```bash
grep -rn "System.exit" src/ | head -20
ls hs_err_pid*.log 2>/dev/null || echo "no crash logs"
```
Expected output: System.exit calls are found or ruled out, and native crashes are confirmed or excluded.

### 4. Rerun with one fork and debug output
A single fork with the summary printed makes the death reproducible and visible. If it still dies silently, the cause is environmental; if it prints an error, that error is the fix target.
```bash
mvn test -DforkCount=1 -Dsurefire.printSummary=true -e
```
Expected output: the rerun either surfaces the real error or proves the death is environmental, and the fork survives with corrected memory or the exit call removed.

## Tool compatibility
- Maven Surefire 2.22 - 3.x
- JDK 11 - 21
- Linux CI runners with constrained memory

## Variant phrasings
- The forked VM terminated without properly saying goodbye
- surefire Process crashed / Corrupted STDOUT
- surefire reports show Stream closed with no summary

## Why it happens
Surefire runs tests in a forked JVM, and when that JVM dies the parent Maven process only sees the broken pipe. The three common killers are heap exhaustion (CI runners are small), System.exit() in code under test, and native crashes from JNI libraries. All three look identical from Maven's side, which is why the reports and a single-fork rerun are needed to tell them apart.

## Edge cases / pitfalls
- JaCoCo's agent rewrites argLine: use the @{argLine} late-replacement placeholder or the agent silently drops your -Xmx
- forkCount=0 runs tests in the Maven JVM itself and hides fork deaths differently; it is a diagnostic tool, not a fix
- a dependency upgrade can introduce a System.exit() you never wrote; check the dependency tree when the death starts after an upgrade
- parallel test execution multiplies fork memory; reduce forkCount before raising -Xmx past the runner's RAM

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_bN781yeTS-HsolVmf8Zdsw
