skip to content

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