skip to content

System Properties & Environment

Passing configuration into tests through system properties and environment variables without wrecking up-to-date checks. Interviewers ask because an unstable property value makes the test task rerun on every build.

on this pageshow

questions

5

How do you pass a system property from a Gradle build into your test JVM, and how do you read it inside the test code?

level: juniorimportance: must knowfreq 70%

answer

  1. forked test JVM ≠ daemon
  2. Test.systemProperty()
  3. systemProperties map
  4. System.getProperty in test
  5. forward -P via providers.gradleProperty

basics

~10 s

Configure the Test task with systemProperty("key", "value") (or test { systemProperty ... }). In the test, read it with System.getProperty("key"). It is set on the forked test JVM, not the build JVM.

solid answer

~40 s

Gradle's `Test` task type exposes `systemProperty(String, Object)` to set a single JVM system property on the **forked test process**, and `systemProperties` (a `Map`) to set several at once. You configure it in the `test { }` block (or any task of type `Test`). Inside the test you read it with `System.getProperty("key")`. The key point is that the test runs in a separate JVM (forked from the Gradle daemon), so a property set on the Gradle daemon's JVM is **not** automatically visible to tests — you must route it through the `Test` task. To bridge a value from the command line, read it in the build script via `providers.gradleProperty(...)` or `findProperty(...)` and forward it with `systemProperty`. This keeps the test's configuration explicit and reproducible.

code

kotlin · 7 lines
kotlin
tasks.test {
    systemProperty("app.env", "ci")
    systemProperties["feature.flag"] = "true"
}

// In the test:
// val env = System.getProperty("app.env")

go deeper

for a junior

Know systemProperty(...) sets a -D on the test JVM and you read it with System.getProperty in the test.

for a middle

Explain the daemon-vs-fork distinction and forward command-line -P values via providers.gradleProperty.

for a senior

Discuss reproducibility, avoiding reads of daemon state, and how this interacts with up-to-date checks.

for a principal

Standardize a project convention for test config injection (e.g. a shared convention plugin) so values are explicit, cacheable, and consistent across modules.

## The problem: two different JVMs When Gradle runs your tests, the `Test` task launches a **separate, forked JVM** to execute them. This is distinct from the Gradle daemon JVM that evaluates your build script. A consequence: a system property set on the build (e.g. via `-Dfoo=bar` on the Gradle command line, or `System.setProperty` in a plugin) does **not** automatically appear in your test code. You must explicitly forward it to the test JVM. ## Setting properties on the Test task The `Test` task (the `test` task created by the `java` plugin is of type `org.gradle.api.tasks.testing.Test`) provides: - `systemProperty(name, value)` — add a single property. - `systemProperties` — a mutable `Map<String, Object>` you can assign or `putAll` into. ```kotlin tasks.test { systemProperty("app.env", "ci") systemProperties["db.timeout"] = "5000" } ``` These become `-Dapp.env=ci -Ddb.timeout=5000` on the forked test JVM's command line. ## Reading in test code Inside a JUnit/TestNG test you read them with standard JDK calls: ```kotlin val env = System.getProperty("app.env") ``` ## Bridging from the command line A common pattern is letting a CI pipeline override a value. Pull it from a Gradle property (passed with `-P`) and forward it: ```kotlin tasks.test { systemProperty("app.env", providers.gradleProperty("appEnv").getOrElse("local")) } ``` Now `./gradlew test -PappEnv=staging` reaches the test as `System.getProperty("app.env") == "staging"`. ## Why not just rely on `-D` to Gradle? Because `-Dfoo=bar` passed to `gradlew` sets the property on the **daemon**, not the test fork. It only reaches tests if you also forward it (e.g. `systemProperty("foo", System.getProperty("foo"))`), and doing that reads daemon state, which is brittle and hurts up-to-date checking. Forwarding through the `Test` task explicitly is the clean approach.

  • If you run ./gradlew test -Dfoo=bar, why might System.getProperty("foo") be null in your test?
    Because -D sets the property on the Gradle daemon JVM, not the forked test JVM. You must forward it via Test.systemProperty("foo", ...) for the test to see it.
  • What's the difference between systemProperty(...) and systemProperties[...] = ...?
    systemProperty(name, value) adds one entry; systemProperties is the underlying mutable Map you can assign or putAll into. Both end up as -D flags on the test fork.

saying these in an interview costs you the question

  • Claiming -DsomeProp passed to gradlew is automatically visible to test code
  • Confusing the build JVM with the forked test JVM
  • Using System.setProperty in the build script and expecting it to reach tests

context

open as a page

How do you set environment variables for the test JVM in Gradle, and how does that differ from system properties?

level: middleimportance: must knowfreq 55%

basics

~10 s

Use Test.environment("KEY", "value") or the environment map. Read it in tests with System.getenv("KEY"). Unlike systemProperty (a -D flag read via System.getProperty), this sets a real process environment variable.

open as a page

How can passing system properties or environment variables into the Test task break Gradle's up-to-date checks or build cache, and how do you avoid it?

level: seniorimportance: must knowfreq 45%

basics

~20 s

systemProperties and environment are tracked task inputs. If you feed them volatile values (timestamps, random ports, absolute paths, build time), the Test task is never up to date and cache hits fail. Use stable values, or mark non-essential properties as untracked inputs.

open as a page

Show how to forward several system properties at once into tests and how to bridge values from Gradle properties or the command line in a configuration-cache-friendly way.

level: middleimportance: should knowfreq 35%

basics

~10 s

Use systemProperties = mapOf(...) or putAll to set many at once. To bridge a -P value safely, read it via providers.gradleProperty("x") (a Provider) rather than project.property at execution time, keeping it configuration-cache compatible.

open as a page

You maintain a multi-module build where tests need consistent, prod-parity configuration injected (env vars and system properties) across modules. How would you architect this so it stays reproducible, cacheable, and secret-safe?

level: principalimportance: should knowfreq 22%

basics

~20 s

Centralize test config in a convention plugin that configures every Test task: stable systemProperty/environment values from providers, no volatile inputs, no real secrets in build inputs. Inject secrets at runtime via env from the CI platform, kept out of tracked inputs.

open as a page