skip to content

You pass -Ddb.url=... on the command line but your test fails to read it. Why, and how do you make it visible to the tests?

level: middleimportance: must knowfreq 55%

answer

  1. daemon JVM vs forked worker JVM
  2. -D not inherited across fork
  3. tasks.test { systemProperty(...) } to forward
  4. -P project props never reach workers directly
  5. providers.* keeps inputs tracked

basics

~10 s

Tests run in a forked JVM, so the daemon's -D system property isn't inherited. Propagate it explicitly: tasks.test { systemProperty("db.url", providers.systemProperty("db.url").get()) }.

solid answer

~40 s

`-Ddb.url=...` sets a system property on the **Gradle daemon JVM**. But `Test`, `JavaExec`, and other forking tasks spawn a **separate worker JVM**, and system properties are not inherited across that boundary. So `System.getProperty("db.url")` inside the test returns `null`. To fix it, forward the value onto the forking task: ```kotlin tasks.test { systemProperty("db.url", providers.systemProperty("db.url").getOrElse("jdbc:h2:mem:test")) } ``` Use `providers.systemProperty(...)` rather than `System.getProperty(...)` so the value is declared as a tracked input for the configuration cache and up-to-date checks. The same applies to `-P` project properties: read them in the script with `providers.gradleProperty(...)` and forward them onto the worker JVM via `systemProperty(...)` (since the worker has no notion of Gradle project properties at all).

code

kotlin · 6 lines
kotlin
tasks.test {
    // Forward selected daemon system properties into the test worker
    systemProperty("db.url", providers.systemProperty("db.url").getOrElse("jdbc:h2:mem:test"))
    // Surface a -P project property to test code as a system property
    systemProperty("app.env", providers.gradleProperty("appEnv").getOrElse("local"))
}

go deeper

for a junior

Recognize that tests fork a separate JVM and need the property forwarded with systemProperty.

for a middle

Show the tasks.test { systemProperty(...) } fix and explain the daemon-vs-worker boundary clearly.

for a senior

Use providers.* for cache-tracked inputs and discuss why project properties never cross the fork.

for a principal

Establish a pattern: narrowly forward named properties (not the whole property set) to keep workers hermetic and builds cacheable across CI.

## The forked-JVM boundary Gradle's daemon is one JVM. Tasks like `Test`, `JavaExec`, `JavaCompile` (with forking), and custom `WorkerExecutor` actions often run in **separate worker JVMs** for isolation. System properties set on the daemon — whether via `-D` on the CLI or `systemProp.` in `gradle.properties` — live only on the daemon and are **not** inherited by those workers. ### Symptom ```bash gradle test -Ddb.url=jdbc:postgresql://ci/app ``` Inside the test: `System.getProperty("db.url")` is `null`, because the test worker is a fresh JVM. ### Fix: explicit propagation The forking task exposes a `systemProperty(name, value)` / `systemProperties(map)` API that injects properties into the worker: ```kotlin tasks.test { // Forward a -D value through to the worker systemProperty("db.url", providers.systemProperty("db.url").getOrElse("jdbc:h2:mem:test")) // Or forward a -P project property systemProperty("app.env", providers.gradleProperty("appEnv").getOrElse("local")) } ``` ### Project properties never cross the boundary on their own A worker JVM has no `Project` object, so `-P` values are *never* directly visible there. The only way to surface a project property in worker code is to convert it into something the worker understands — a system property (`systemProperty(...)`), an environment variable (`environment(...)`), or a generated resource/argument. ### Why use the providers API Reading via `providers.systemProperty`/`providers.gradleProperty` and forwarding the provider declares the value as a build input. That keeps up-to-date checks and the configuration cache correct: change `-Ddb.url` and the test task re-runs; eager `System.getProperty` reads risk being treated as undeclared inputs. ### Bulk forwarding (use sparingly) You can forward many at once, but forwarding *all* daemon system properties couples the worker to ambient state and hurts cacheability: ```kotlin tasks.test { systemProperties(System.getProperties().mapKeys { it.key.toString() }) } // discouraged ``` Prefer naming exactly the properties the tests need.

  • Why can't a forked test read a -P project property at all without help?
    Worker JVMs have no Gradle Project object; project properties are a build-model concept. You must translate it into a system property, env var, or generated input.
  • What is the downside of forwarding all System.getProperties() to the test worker?
    It couples tests to ambient daemon state, leaks unrelated properties, and harms up-to-date/configuration-cache correctness. Forward only what the tests need.
  • How would you instead pass the value as an environment variable?
    Use tasks.test { environment("DB_URL", provider) } and read System.getenv("DB_URL") in the test.

saying these in an interview costs you the question

  • Assuming -D on the daemon is automatically visible to test workers.
  • Trying to read a -P project property directly inside test code via project.property.

context