skip to content

What is dependency locking in Gradle, and why would you enable it?

level: juniorimportance: must knowfreq 55%

answer

  1. pins resolved versions
  2. gradle.lockfile per project
  3. dynamic versions / ranges drift
  4. --write-locks to generate
  5. reproducible builds, commit lockfile

basics

~20 s

Dependency locking pins the exact versions a configuration resolves to, saving them in a lockfile. With dynamic versions or version ranges, builds can drift over time; locking makes resolution reproducible across machines and over time.

solid answer

~40 s

Dependency locking records the exact resolved versions of a configuration into a `gradle.lockfile`, so subsequent builds resolve to those same versions instead of re-resolving freely. It matters mainly when you use **dynamic versions** (`1.+`, `latest.release`) or **version ranges** (`[1.0,2.0)`), where the resolved version can change as new artifacts are published. You opt in with a `dependencyLocking { ... }` block (commonly `lockAllConfigurations()`) and generate the lockfile with `./gradlew dependencies --write-locks` (or any task `--write-locks`). After that, normal builds verify resolution against the lockfile and fail if it would differ. The goal is **reproducible builds**: the same source plus the same lockfile yields the same dependency graph on CI, on a teammate's machine, and months later.

code

kotlin · 11 lines
kotlin
// build.gradle.kts
dependencyLocking {
    lockAllConfigurations()
}

dependencies {
    implementation("org.springframework:spring-core:[5.0,6.0)") // dynamic range
}

// generate: ./gradlew dependencies --write-locks
// later builds verify against gradle.lockfile

go deeper

for a junior

Know it pins resolved versions into a lockfile for reproducible builds and that you generate it with --write-locks.

for a middle

Explain the dynamic-version drift problem and that locking verifies resolution and fails on mismatch.

for a senior

Discuss committing the lockfile, the single gradle.lockfile format, and when locking adds little value (fully fixed versions).

for a principal

Frame as a supply-chain / reproducibility control alongside dependency verification, and weigh per-team policy for enabling it across many modules.

## The problem locking solves Gradle resolves each *configuration* (a named set of dependencies, e.g. `runtimeClasspath`) into a concrete dependency graph. If every dependency is declared with a fixed version (`1.2.3`), resolution is already deterministic for a given build. But two common cases break that: - **Dynamic versions** — `1.+`, `latest.release`, `[1.0,2.0)`. Gradle picks the highest matching version *available at resolution time*, so a newly published artifact silently changes your build. - **Transitive drift** — even with fixed direct versions, a transitive dependency declared dynamically by some library can move. Dependency locking makes resolution **reproducible** by recording the exact versions chosen and pinning them on later builds. ## How it works 1. **Enable** locking in the build script: ```kotlin dependencyLocking { lockAllConfigurations() } ``` 2. **Generate** the lockfile by resolving with the write flag: `./gradlew dependencies --write-locks`. 3. Gradle writes a single `gradle.lockfile` per project (legacy mode used one file per configuration under `gradle/dependency-locks/`). 4. On **subsequent builds**, when a locked configuration resolves, Gradle checks the result against the lockfile. If the would-be resolution differs from the locked versions, the build **fails** with a lock mismatch error. ## The lockfile `gradle.lockfile` is a plain-text, line-oriented file. Each line is `group:artifact:version=<comma-separated list of configurations>` plus an `empty=` line listing configurations that resolved to nothing. It is meant to be **committed to version control** so everyone shares the same pins. ``` empty=annotationProcessor,testAnnotationProcessor org.apache.commons:commons-lang3:3.12.0=compileClasspath,runtimeClasspath ``` ## When to use it - You rely on dynamic/range versions and want reproducibility. - You want CI and developer machines to be byte-for-byte consistent. - You want a reviewable audit trail of dependency changes (a lockfile diff in a PR shows exactly what moved). If every version is already fixed, locking adds little resolution value but still gives you an explicit, reviewable snapshot of the full transitive graph.

  • Does locking help if all your versions are already hard-coded?
    Resolution can't drift in that case, so the reproducibility benefit is minimal. But the lockfile still gives an explicit, committed snapshot of the entire transitive graph, which is useful for review and audit.
  • Where should the lockfile live?
    In version control alongside the build script (`gradle.lockfile` at the project root), so every machine and CI run resolves to the same pinned versions.

Like a package-lock.json / Pipfile.lock: you declare loose constraints, but the lockfile freezes the exact resolved graph so every build is identical.

saying these in an interview costs you the question

  • Claiming locking changes which versions Gradle picks — it only records and then enforces them.
  • Saying it's only useful with dynamic versions; it also documents the full transitive graph.

context