skip to content

A teammate reports that Gradle keeps starting new daemons and builds feel slow, even though a daemon should already be running. What would you investigate?

level: middleimportance: should knowfreq 35%

answer

  1. new daemon every build = nothing compatible
  2. JAVA_HOME differs (IDE vs terminal vs CI)
  3. conflicting org.gradle.jvmargs / per-command args
  4. mixed Gradle versions -> separate registries
  5. fix: standardize JDK + jvmargs + wrapper

basics

~10 s

Likely each build requests a different JVM or JVM args, so no existing daemon is compatible. Check that JAVA_HOME and org.gradle.jvmargs are consistent across every invocation.

solid answer

~50 s

Constant fresh daemons mean **no idle daemon is compatible**, so the client keeps spawning new ones. The usual culprits: **`JAVA_HOME` differs between shells/IDE/CI**, so each invocation needs a daemon on a different JDK; **`org.gradle.jvmargs` differs** between project and user `gradle.properties`, or someone passes JVM args **on the command line** per build (which can even force single-use daemons); or builds run under **different Gradle versions** (each has its own registry). I'd compare the resolved Java home and JVM args across invocations, inspect `~/.gradle/daemon/<version>/` for many entries with different parameters, and check for an idle daemon that simply never matches. The fix is to **standardize** the JDK (ideally pin via `org.gradle.java.home` or a consistent toolchain launcher) and JVM args so every build converges on one reusable daemon. Memory pressure can also cause Gradle to expire daemons aggressively, masking as 'no reuse'.

code

bash · 4 lines
bash
# Diagnose identity drift
./gradlew --version    # shows Gradle version + JVM the daemon will use
echo "$JAVA_HOME"      # compare to org.gradle.java.home and the IDE's JDK
ls -lt ~/.gradle/daemon/8.7/   # many fresh daemons => proliferation

go deeper

for a junior

Suspect different Java versions or JVM args between runs; check JAVA_HOME is consistent.

for a middle

Systematically check JDK, jvmargs, and Gradle version as identity factors and inspect the daemon registry for proliferation.

for a senior

Distinguish identity-mismatch spawns from memory-pressure expiry; prescribe pinning JDK/jvmargs and using the wrapper everywhere.

for a principal

Establish org-wide toolchain/JVM-arg standards and CI agent configs so identity drift is structurally prevented.

## Symptom: 'Starting a Gradle Daemon' every time If reuse worked, you'd see that line once and then silence on later builds. Seeing it repeatedly means **every invocation fails the compatibility test** against the daemons already registered, so a new one is forked each time. This both wastes startup time (cold JIT) and piles up daemons that hold heap (**daemon proliferation**). ## Investigate the identity factors Compatibility hinges on JDK + JVM args + version. Walk each: - **JAVA_HOME / resolved JDK** — the IDE may launch Gradle on one JDK while the terminal uses another; `org.gradle.java.home` may differ from the shell's `JAVA_HOME`. Each distinct Java home needs its own daemon. This is the most frequent cause. - **org.gradle.jvmargs** — a project `gradle.properties` and the user-level `~/.gradle/gradle.properties` may set conflicting args; or CI passes different `-Xmx`. Mismatched args => incompatible. - **Command-line JVM overrides** — passing JVM args per invocation can force **single-use** daemons that never persist, so you never see reuse at all. - **Gradle version** — wrapper vs. system Gradle, or mixed versions across modules, split daemons across per-version registries. ## How to confirm ```bash # Compare what each environment actually resolves ./gradlew --version # prints the JVM and Gradle version in use echo "$JAVA_HOME" # shell's JDK ls ~/.gradle/daemon/ # one dir per version ``` Look in the per-version daemon directory for many recently created daemons, and check whether an idle one exists whose parameters never match your request. ## The fix Converge identities: - Pin the JDK the daemon runs on (consistent `JAVA_HOME` / `org.gradle.java.home`, or a uniform toolchain launcher) across terminal, IDE, and CI. - Put all stable JVM args in **one** authoritative `gradle.properties`; stop passing them per command. - Use the **wrapper** everywhere so the Gradle version is identical. After that, almost every invocation finds the same idle daemon and reuses it. Note too that under memory pressure Gradle may **expire** idle daemons, which can also look like 'no reuse' — but the dominant cause of *new spawns every build* is identity mismatch.

  • Why would the IDE and the terminal end up using different daemons?
    They may launch Gradle on different JDKs (different JAVA_HOME / toolchain), so each needs a daemon with a matching Java home; the daemons are not interchangeable.
  • Besides identity mismatch, what else can look like 'no reuse'?
    Under memory pressure Gradle may expire idle daemons early, so a daemon you expected to reuse is gone; this manifests similarly but stems from resource limits, not incompatibility.
  • What single change usually fixes most proliferation?
    Standardizing the JDK (consistent JAVA_HOME / org.gradle.java.home) across terminal, IDE, and CI so all invocations share one daemon.

saying these in an interview costs you the question

  • Blaming the build script logic rather than JVM/identity drift.
  • Recommending more heap when the real issue is each build requesting a different identity.
  • Ignoring per-command JVM-arg overrides that force single-use forks.

context