skip to content

A property has an unexpected value at build time and you suspect a gradle.properties conflict. How do you reason about and confirm which source supplied it?

level: seniorimportance: should knowfreq 40%

answer

  1. walk ladder top-down, stop at first definer
  2. register print task with providers.gradleProperty
  3. same repo file but differs per env = higher layer
  4. stale ~/.gradle line silently overrides
  5. env var invisible in files

basics

~10 s

Walk the precedence ladder top-down: check command-line/env overrides, then ~/.gradle/gradle.properties, then the project-root file — the highest source that defines the key wins. Print the resolved value in a task to confirm.

solid answer

~50 s

I treat it as resolving the precedence ladder. The same key can be set on the **command line / env**, in **`GRADLE_USER_HOME/gradle.properties`**, and in the **project-root file**, and the highest source that defines it wins (command line > user-home > project file > default). So I check those sources in that order and stop at the first that defines the key. To *confirm* rather than guess, I register a tiny diagnostic task that prints the resolved value — `providers.gradleProperty(name).getOrElse("<unset>")` — and run it; if it differs from the project-root file, an override is coming from user-home or an injected `ORG_GRADLE_PROJECT_*`/`-P`. Common culprits are a forgotten line in `~/.gradle/gradle.properties` (which silently overrides the committed default) or a CI env var. I also keep in mind that command-line values are per-invocation only, so a value that's 'wrong' interactively but 'right' on CI usually points at an env-var or user-home difference between the two environments.

code

kotlin · 7 lines
kotlin
tasks.register("printProp") {
    val name = "buildEnv"
    val value = providers.gradleProperty(name) // resolves through full precedence
    doLast { println("$name = ${value.getOrElse("<unset>")}") }
}
// ./gradlew printProp                  -> shows the winning value
// ./gradlew printProp -PbuildEnv=probe -> confirms command-line override path

go deeper

for a junior

Know to check the obvious files and that command line beats them.

for a middle

Walk the full ladder top-down and identify the first source that defines the key.

for a senior

Add an empirical confirmation step (print task, sentinel override) and use the 'same repo file but differs by environment ⇒ higher layer' heuristic.

for a principal

Generalize to fleet debugging: standardize CI env, audit ~/.gradle drift, and add build-time logging/validation so property provenance is observable across many repos.

## The systematic approach An 'unexpected property value' is almost always a precedence question. Because Gradle resolves a single winner from a fixed ladder, debugging is just **walking that ladder top-down and finding the first source that defines the key**: 1. Invocation overrides (`-Pkey=...`, `ORG_GRADLE_PROJECT_key`, system props) — highest. 2. `GRADLE_USER_HOME/gradle.properties` (`~/.gradle`). 3. Project-root `gradle.properties`. 4. Built-in default — lowest. The winner is the highest of those that actually sets `key`. ## Confirm, don't assume Reading the files in your head is error-prone (an env var or a stale user-home line is easy to miss). Make the build tell you: ```kotlin // build.gradle.kts tasks.register("printProp") { val name = "buildEnv" val value = providers.gradleProperty(name) // resolves via full precedence doLast { println("$name = ${value.getOrElse("<unset>")}") } } ``` Run `./gradlew printProp`. Then probe the layers: - Run again with `-PbuildEnv=sentinel` — if the output changes to `sentinel`, command-line override works and nothing higher is masking it. - Inspect `~/.gradle/gradle.properties` for the key; if present, that's overriding your committed default (user-home outranks project root). - Check the environment for `ORG_GRADLE_PROJECT_buildEnv`; injected env vars are invisible in any file. ## Why the same build differs by environment A frequent real-world symptom: the value is correct on a teammate's machine or CI but wrong on yours (or vice-versa). Since the **project-root file is identical** (it's in Git), the divergence must come from a *higher, non-committed* layer — typically a personal `~/.gradle` entry or an env var present in one environment only. That immediately narrows the hunt. ## Practical checklist - Is it set on the command line for this run? (per-invocation, won't persist) - Is it in `~/.gradle/gradle.properties`? (silently beats the repo file) - Is there an `ORG_GRADLE_PROJECT_*`/`systemProp.*` env var? - Only then: the project-root file, then Gradle's default. Resolve top-down, confirm with a print task, and the 'mystery' value stops being mysterious.

  • The committed file and your output agree on your laptop but CI shows a different value. Where do you look first?
    A higher, non-committed layer on CI: an `ORG_GRADLE_PROJECT_*` env var or a runtime-written `GRADLE_USER_HOME` file — since the in-repo file is identical, the difference must be above it.
  • Why prefer a print task over just opening the files?
    Files don't reveal injected env vars or command-line overrides, and you can misread which of several files wins. The print task resolves through the actual precedence and shows the real value.
  • A command-line `-P` 'fixed' the value but it's wrong again next build. Why?
    Command-line values apply only to that single invocation; they don't persist. The underlying file/env source is still supplying the unexpected value.

saying these in an interview costs you the question

  • Editing the project-root file repeatedly without checking whether user-home or an env var is overriding it.
  • Assuming the committed file is authoritative when a higher layer exists.
  • Forgetting that command-line overrides are per-invocation.

context