skip to content

A build script reads project.property("flavor") but users often run without -Pflavor. How do you read it safely with a default?

level: juniorimportance: must knowfreq 60%

answer

  1. property() throws, findProperty() returns null
  2. hasProperty() presence guard
  3. providers.gradleProperty().getOrElse(default)
  4. lazy provider = cache-tracked input
  5. CLI props are stringly-typed

basics

~10 s

project.property throws if the property is missing. Use findProperty("flavor") (returns null) with an Elvis default, or providers.gradleProperty("flavor").getOrElse("default").

solid answer

~40 s

`project.property("flavor")` throws a `MissingPropertyException` when `-Pflavor` wasn't passed and it isn't in any `gradle.properties`. For optional inputs you want a graceful default. Three idiomatic options: - `project.findProperty("flavor") ?: "full"` — `findProperty` returns `null` instead of throwing. - `project.hasProperty("flavor")` — guard before reading. - `providers.gradleProperty("flavor").getOrElse("full")` — the modern, configuration-cache-friendly form that returns a lazy `Provider<String>` and supplies the default. The `providers` approach is preferred because it declares the property as a tracked build input, so the configuration cache invalidates when the value changes, and it composes with `Property`/`map`/`orElse`. Reach for `property(...)` only when a missing value should genuinely fail the build (a required parameter).

code

kotlin · 10 lines
kotlin
// Preferred lazy form with default and type conversion
val verbose: Provider<Boolean> =
    providers.gradleProperty("verbose")
        .map { it.toBoolean() }
        .orElse(false)

tasks.register("report") {
    inputs.property("verbose", verbose)
    doLast { if (verbose.get()) println("verbose mode") }
}

go deeper

for a junior

Know that property() throws and findProperty()/Elvis gives a default.

for a middle

Reach for providers.gradleProperty(...).orElse(...) and explain stringly-typed conversion.

for a senior

Justify the lazy provider as a configuration-cache input and show wiring into a task Property.

for a principal

Set a team convention: required params throw, optional ones default via providers; avoid eager configuration-time reads to keep the cache effective.

## The four project-property read APIs Given `-Pflavor=lite` (or `flavor=lite` in gradle.properties), four APIs read it, differing in their missing-value behavior: | API | Missing behavior | Returns | |---|---|---| | `project.property("flavor")` | throws `MissingPropertyException` | `Any` | | `project.findProperty("flavor")` | returns `null` | `Any?` | | `project.hasProperty("flavor")` | — | `Boolean` (presence check) | | `providers.gradleProperty("flavor")` | empty provider | `Provider<String>` | ### Defaulting patterns ```kotlin // Elvis with findProperty val flavor = (findProperty("flavor") as String?) ?: "full" // Guard with hasProperty if (hasProperty("flavor")) { /* ... */ } // Lazy, cache-safe (preferred) val flavorProvider: Provider<String> = providers.gradleProperty("flavor").orElse("full") ``` ### Why `providers.gradleProperty` is preferred Under the **configuration cache**, Gradle must know every input that influences configuration. Calling `project.property` eagerly at configuration time reads the value immediately and, when used at execution time, can be reported as an undeclared input. `providers.gradleProperty("flavor")` returns a `Provider` whose value is resolved lazily and **registered as a build input**, so changing `-Pflavor` correctly invalidates the cached configuration. It also chains cleanly: `.orElse(...)`, `.map { ... }`, and feeds straight into a task's `Property<String>` via `.set(provider)`. ### A note on types Project properties are stringly-typed on the CLI. `findProperty` returns `Any?` (often a `String`), so cast or convert (`.toBoolean()`, `.toInt()`) deliberately. The `providers` API is typed `Provider<String>`, making conversions explicit via `.map { it.toBoolean() }`.

  • When is project.property (the throwing variant) actually the right choice?
    When the property is a required build parameter and its absence should fail fast with a clear error rather than silently using a default.
  • Why prefer providers.gradleProperty over findProperty under the configuration cache?
    It registers the property as a tracked input so the cache invalidates when the value changes, and avoids eager reads flagged as undeclared inputs.

saying these in an interview costs you the question

  • Using project.property for an optional flag and being surprised by MissingPropertyException.
  • Reading findProperty as if it were already a String without acknowledging Any?/null.

context