skip to content

What is the difference between passing -Pname=value and -Dname=value on the Gradle command line?

level: juniorimportance: must knowfreq 70%

answer

  1. -P = project property, -D = JVM system property
  2. project.property vs System.getProperty
  3. gradle.properties: bare key vs systemProp.
  4. providers.gradleProperty / providers.systemProperty
  5. -D not auto-propagated to forked JVMs

basics

~10 s

-P sets a Gradle project property, read via project.property("name"). -D sets a JVM system property, read via System.getProperty("name"). -P is Gradle-specific; -D is standard JVM.

solid answer

~40 s

`-Pname=value` defines a **project property** on the Gradle `Project` object. You read it in the build script with `project.property("name")`, `findProperty("name")`, or the `hasProperty`/`providers.gradleProperty` APIs. It is purely a Gradle concept. `-Dname=value` sets a **JVM system property** on the Gradle daemon's JVM (the standard `java -D` mechanism). You read it with `System.getProperty("name")` or `providers.systemProperty("name")`. It also reaches forked processes only if you propagate it. The practical distinction: `-P` is for parameterizing the build itself (toggling a flavor, passing a version), while `-D` is for JVM/tooling configuration and properties consumed by code (including `org.gradle.*` daemon flags). Both can also be set in `gradle.properties` — keys there with no prefix become project properties, and `systemProp.foo=bar` becomes the system property `foo`.

code

kotlin · 10 lines
kotlin
// build.gradle.kts
// gradle build -PreleaseType=rc -DbuildId=42
val releaseType = providers.gradleProperty("releaseType").getOrElse("snapshot")
val buildId = providers.systemProperty("buildId").getOrElse("local")

tasks.register("info") {
    doLast {
        println("releaseType=$releaseType buildId=$buildId")
    }
}

go deeper

for a junior

State the two read APIs correctly: -P -> project.property, -D -> System.getProperty. That alone is a passing answer.

for a middle

Map both to gradle.properties (bare key vs systemProp.) and mention the lazy providers.* APIs.

for a senior

Discuss configuration-cache implications and that -D is not auto-propagated to forked JVMs/test workers.

for a principal

Frame a team convention: -P for build parameterization, -D reserved for JVM/daemon tuning; standardize via gradle.properties to keep CLI invocations reproducible.

## Two different namespaces Gradle exposes two distinct ways to pass key/value data on the command line, and they land in **two different places**. ### `-P` — project properties `-Pname=value` (or `--project-prop name=value`) sets a property on the Gradle `Project` model. These are the natural way to parameterize a build. You read them with: - `project.property("name")` — throws if missing - `project.findProperty("name")` — returns `null` if missing - `project.hasProperty("name")` - `providers.gradleProperty("name")` — the configuration-cache-safe, lazy `Provider<String>` API (preferred in modern Gradle) Project properties can also be declared in a `gradle.properties` file with a bare key: ```properties myFlag=true ``` ### `-D` — JVM system properties `-Dname=value` sets an ordinary **JVM system property** on the Gradle daemon process — exactly like `java -Dname=value`. You read them with `System.getProperty("name")` or, lazily, `providers.systemProperty("name")`. In `gradle.properties` the same thing is written with the `systemProp.` prefix: ```properties systemProp.name=value ``` ### Why both exist `-P` is Gradle's own configuration channel — it never collides with the JVM. `-D` is the universal JVM channel, used both by Gradle internals (e.g. `-Dorg.gradle.*` flags configure the daemon) and by any library that reads system properties. Because `-D` properties live on the daemon JVM, they are **not** automatically visible to forked JVMs (test workers, `JavaExec`); you must propagate them explicitly via `systemProperty(...)` on the task. ### Mapping summary | Source | Project property | System property | |---|---|---| | CLI | `-Pkey=value` | `-Dkey=value` | | gradle.properties | `key=value` | `systemProp.key=value` | | read API | `project.property` / `providers.gradleProperty` | `System.getProperty` / `providers.systemProperty` | ### Lazy access matters Under the configuration cache, calling `System.getProperty` or `project.property` directly at execution time can be flagged as an undeclared input. The `providers.gradleProperty`/`providers.systemProperty` APIs declare the value as a tracked build input, so the cache invalidates correctly when it changes.

  • Where do you put the equivalent of -Pfoo=bar and -Dfoo=bar in gradle.properties?
    `foo=bar` (bare key) for the project property, and `systemProp.foo=bar` for the system property.
  • Why might a -D property not show up inside your test code?
    Tests run in a forked JVM. The -D only set the daemon's JVM; you must propagate it with `tasks.test { systemProperty("foo", ...) }` (or `systemProperties(...)`).

saying these in an interview costs you the question

  • Claiming -P and -D are interchangeable aliases for the same thing.
  • Saying -D project properties are read with project.property (they are system properties).

context