A build script reads project.property("flavor") but users often run without -Pflavor. How do you read it safely with a default?
answer
- property() throws, findProperty() returns null
- hasProperty() presence guard
- providers.gradleProperty().getOrElse(default)
- lazy provider = cache-tracked input
- CLI props are stringly-typed
basics
~10 sproject.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// 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
Know that property() throws and findProperty()/Elvis gives a default.
Reach for providers.gradleProperty(...).orElse(...) and explain stringly-typed conversion.
Justify the lazy provider as a configuration-cache input and show wiring into a task Property.
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.