What is the difference between a dynamic version and a changing version in Gradle dependency management?
answer
- dynamic = which version (range/selector)
- changing = same coord, mutable bytes
- -SNAPSHOT auto-changing
- 24h default cache
- cacheDynamicVersionsFor / cacheChangingModulesFor
basics
~20 sA dynamic version lets Gradle pick which version to resolve (e.g. '1.+', 'latest.release'). A changing version is one fixed coordinate whose artifacts can change over time (e.g. '-SNAPSHOT'), so the same version number may hold different content.
solid answer
~40 sThese are two distinct kinds of non-deterministic dependencies. **Dynamic version** — you don't pin one number; you give Gradle a *range* or *selector* and it resolves the highest matching version: `'1.+'`, `'[1.0,2.0)'`, `'latest.release'`, `'latest.integration'`. The version coordinate itself is computed at resolution time. **Changing version** — the coordinate is fixed (e.g. `1.0.0-SNAPSHOT`) but the *artifacts behind it* can be republished. SNAPSHOT versions are treated as changing automatically; you can also mark any module changing with `changing = true`. Gradle caches both for 24 hours by default. You control freshness via `resolutionStrategy.cacheDynamicVersionsFor` and `cacheChangingModulesFor`. Both reduce build reproducibility, so for releases you should pin exact versions or use dependency locking.
code
kotlin · 7 linesdependencies {
implementation("org.example:lib:1.+") // dynamic
implementation("org.example:api:2.0.0-SNAPSHOT") // changing (auto)
implementation("org.example:tool:1.0.0") { // forced changing
isChanging = true
}
}go deeper
State the one-line distinction: dynamic = range/selector picks the version; changing = fixed version with mutable artifacts (-SNAPSHOT).
Add the default 24h caching and the two resolutionStrategy knobs, and give concrete syntax examples for each.
Discuss reproducibility trade-offs and when to reach for dependency locking instead.
Frame org policy: ban '+' and dynamic versions in release pipelines, enforce locking, and define snapshot-cache windows for CI feedback loops.
## The two axes of non-determinism When you declare a dependency in Gradle you normally write an exact coordinate like `com.google.guava:guava:32.1.3-jre`. That is fully *static* and *fixed*: the version string identifies one immutable artifact. Gradle has two orthogonal mechanisms that relax this. ### Dynamic versions — the *which version* is undecided A dynamic version is a selector that matches a set of versions; Gradle resolves it to the highest matching one available in the repositories at resolution time. - `'1.+'` / `'1.2.+'` — prefix wildcard: highest version starting with that prefix. - `'[1.0,2.0)'` — Maven-style range (inclusive 1.0, exclusive 2.0). Also `(,1.0]`, `[1.0,)`, etc. - `'latest.release'` — highest version whose status is `release`. - `'latest.integration'` — highest version of any status (includes snapshots). - `'+'` — anything (the absolute highest). Strongly discouraged. ### Changing versions — the *coordinate is fixed but contents mutate* A changing module keeps the same version string but its published bytes can change. The classic case is a Maven `-SNAPSHOT`: `1.0.0-SNAPSHOT` is republished on every CI build. Gradle auto-detects the `-SNAPSHOT` suffix and treats such modules as changing. You can also force changing semantics on any dependency: ```kotlin dependencies { implementation("org.example:lib:1.0.0") { isChanging = true } } ``` ### Caching and the resolution strategy By default Gradle caches resolved dynamic versions and changing-module artifacts for **24 hours**. This avoids hitting the network on every build but means a freshly published snapshot may not appear for a day. Override per-configuration: ```kotlin configurations.all { resolutionStrategy { cacheDynamicVersionsFor(10, "minutes") cacheChangingModulesFor(0, "seconds") } } ``` The command-line flag `--refresh-dependencies` forces Gradle to bypass these caches for a single invocation and re-check the network. ### Why it matters Both features trade reproducibility for convenience. Two builds run at different times can resolve different artifacts even from the same source, breaking 'works on my machine'. Production-grade projects therefore prefer static versions plus **dependency locking** (`dependencyLocking { lockAllConfigurations() }`, written to `gradle.lockfile`) so dynamic/changing inputs are pinned to an auditable set.
- Is '2.0.0-SNAPSHOT' dynamic, changing, or both?Changing, not dynamic. The version coordinate is fixed (2.0.0-SNAPSHOT), but Gradle treats the `-SNAPSHOT` suffix as a changing module whose artifacts may be republished.
- Why are both discouraged for release builds?They make builds non-reproducible — the same Git commit can resolve different artifacts depending on when it runs. Pin exact versions or use dependency locking for releases.
Dynamic version = 'give me the latest model car' (the choice is open). Changing version = 'give me car #1234, but the factory keeps re-fitting that exact serial number' (the label is fixed, the contents change).
saying these in an interview costs you the question
- Claiming '-SNAPSHOT' is a dynamic version (it's changing).
- Saying Gradle always hits the network for dynamic/changing deps (it caches them 24h by default).
- Conflating the two: a range selector and a mutable-contents coordinate are different mechanisms.