skip to content

Describe the client/daemon split in a Gradle invocation. What does each side do?

level: middleimportance: must knowfreq 55%

answer

  1. client = thin launcher + I/O relay
  2. daemon = runs init/config/execution
  3. client forwards request over local socket
  4. daemon JVM args via org.gradle.jvmargs
  5. daemon persists; client exits

basics

~20 s

Every gradle run has two parts: a short-lived client that parses arguments and forwards the build request, and a long-lived daemon JVM that actually runs the build. The client streams console output back; the daemon does the work and stays alive afterward.

solid answer

~50 s

A Gradle invocation is split into two roles. The **client** is the process you actually launch via `gradle` or `./gradlew`. It's deliberately lightweight: it reads command-line arguments and `gradle.properties`, locates a compatible idle daemon (or spawns one), then sends the build request over a local connection and relays the daemon's console output and exit code back to your terminal. The **daemon** is the persistent background JVM that performs the build — it runs the **initialization, configuration, and execution** phases, builds the task graph, and executes tasks. After the build it does not exit; it idles, holding warm JIT-compiled code and in-memory caches for the next request. The split exists so the client can start instantly while the expensive, reusable runtime lives in the daemon. Note the daemon runs under **its own JVM args** (`org.gradle.jvmargs`), independent of whatever heap the lightweight client uses.

code

toml · 5 lines
toml
# gradle.properties
# These flags configure the DAEMON JVM (where the build runs),
# not the lightweight client process.
org.gradle.jvmargs=-Xmx2g -Dfile.encoding=UTF-8
org.gradle.daemon=true

go deeper

for a junior

Know there are two parts — a launcher and a background JVM — and the background one runs the build.

for a middle

Articulate each role precisely and that daemon JVM args (org.gradle.jvmargs) tune the daemon, not the client.

for a senior

Connect the split to daemon reuse/compatibility (different JVM args ⇒ different daemon) and to where build memory and crashes actually originate.

for a principal

Use the split to reason about standardizing org.gradle.jvmargs across a team so daemons are reusable and don't proliferate, and to design memory budgets for shared/CI hosts.

## Two roles, one command When you type `./gradlew assemble`, it *looks* like one program runs, but Gradle separates the work into two cooperating processes. ### The client The **client** is the process the OS actually starts. Its job is to get the build request to a daemon and present results — nothing more: 1. Parse command-line arguments (tasks, flags like `--info`, `-P` project properties). 2. Read configuration (`gradle.properties`, env, system properties) to know *which kind* of daemon is needed. 3. **Locate** a running, compatible, idle daemon — or start a new one if none matches. 4. Send the build request to the daemon over a local socket. 5. Stream the daemon's logging/console output back to your terminal and propagate the **exit code**. Because it does no real build work, the client stays small and starts fast. ### The daemon The **daemon** is the long-lived JVM that does everything that matters: - Runs Gradle's build lifecycle — **initialization → configuration → execution**. - Evaluates settings and build scripts, builds the **task graph**, and executes tasks. - Holds the resident state: JIT-warm code, parsed scripts, the configured model, dependency/file metadata. When the build ends, the daemon **stays alive and idle**, ready to serve the next request, until it's idle-expired or stopped. ## Why separate them? If the build ran inside the client, every invocation would be a cold JVM and you'd lose all warmth between runs. Splitting lets the cheap part (arg parsing, I/O relay) be a throwaway process while the **expensive, reusable** part (the configured, warmed-up build runtime) persists. ## A consequence worth knowing: separate JVM tuning Because the daemon is a different JVM from the client, **the daemon has its own memory and JVM flags**, set via `org.gradle.jvmargs` in `gradle.properties`. So if a build runs out of heap, you raise the *daemon's* heap, not the client's: ```properties # gradle.properties — these configure the DAEMON JVM, not the client org.gradle.jvmargs=-Xmx2g -XX:+HeapDumpOnOutOfMemoryError org.gradle.daemon=true ``` This also means a daemon started with one set of JVM args is **not reusable** by a request that needs different args — that request gets (or starts) a different daemon. ## Mental model Think: *client = waiter who takes your order and brings the food back; daemon = the kitchen that actually cooks and stays open between orders.*

  • If a build fails with an OutOfMemoryError, whose heap do you increase?
    The daemon's. The build runs in the daemon JVM, so you raise `org.gradle.jvmargs` (e.g. `-Xmx`) in gradle.properties. The client is lightweight and isn't where the build executes.
  • Does the client run any of the build lifecycle phases?
    No. Initialization, configuration, and execution all run in the daemon. The client only parses args, forwards the request, and relays output and the exit code.

Client is the waiter who takes your order and carries the plate back; the daemon is the kitchen that does the cooking and stays open between orders.

saying these in an interview costs you the question

  • Saying the build runs 'in the gradle command you typed' — that's only the client; the build runs in the daemon.
  • Tuning client heap to fix build OOMs instead of the daemon's `org.gradle.jvmargs`.

context