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?
answer
- forked test JVM ≠ daemon
- Test.systemProperty()
- systemProperties map
- System.getProperty in test
- forward -P via providers.gradleProperty
basics
~10 sConfigure 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 sGradle'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 linestasks.test {
systemProperty("app.env", "ci")
systemProperties["feature.flag"] = "true"
}
// In the test:
// val env = System.getProperty("app.env")go deeper
Know systemProperty(...) sets a -D on the test JVM and you read it with System.getProperty in the test.
Explain the daemon-vs-fork distinction and forward command-line -P values via providers.gradleProperty.
Discuss reproducibility, avoiding reads of daemon state, and how this interacts with up-to-date checks.
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