Coordinates, Dependencies, Repositories, Metadata, Transitivity, and Version Selection: Configuration, Design Choices, and Tradeoffs
Choose fixed versus dynamic versions, direct declarations versus transitive reliance, conflict-management policy, and repository topology deliberately for reproducibility, security, and maintainability.
Learning objectives
- Choose fixed, ranged/dynamic, and changing version policies based on reproducibility and update requirements.
- Decide when a dependency should be declared directly rather than inherited transitively.
- Compare Maven nearest/managed mediation with Gradle conflict resolution and constraints without pretending the models are identical.
- Design public-repository versus mirror/proxy topology with explicit trust, availability, and dependency-confusion boundaries.
- Justify a production dependency policy with observable graph and repository evidence.
org.apache.commons:commons-text:1.10.0, whose published
POM declares org.apache.commons:commons-lang3:3.12.0,
plus a deliberate direct request for commons-lang3:3.9.
Re-check current versions and metadata before reusing these examples
in production.
1. Design policy begins with one question: what is allowed to change without a source review?
A dependency policy is a change-control policy. Fixed coordinates constrain version discovery. Dynamic/ranged selectors delegate part of version choice to repository state. Snapshots/changing modules delegate even more because the same coordinate can point to new bytes. Repositories and mirrors decide which authority supplies the metadata used for those decisions.
2. Fixed versions versus ranges and dynamic selectors
| Policy | Benefit | Risk | Evidence/control |
|---|---|---|---|
| fixed releases | stable, reviewable request identity | still depends on repository integrity and transitive graph | dependency tree/locks/verification/checksums as appropriate |
| Maven version range | allows compatible version discovery | repository/time can change selected version | capture resolved versions; govern update window |
Gradle dynamic selector such as 3.+ |
fast integration with family of releases | new publication can change graph; cached TTL affects timing | dependency locking + controlled update process |
| SNAPSHOT/changing | rapid integration before release | same coordinate can yield different bytes | short-lived integration only; never confuse with immutable release evidence |
Gradle documentation explicitly warns that dynamic/changing dependencies can make builds unreproducible and recommends locking dynamic selections. Locking does not make a changing module immutable; the coordinate can still refer to different bytes.
3. Direct declaration versus “I get it transitively anyway”
If your source directly imports an API, declaring that dependency directly communicates ownership of the contract. Relying on an unrelated library to continue exporting the dependency couples your source to somebody else’s implementation graph. Maven’s own dependency guide recommends explicitly specifying dependencies your source uses directly.
| Situation | Recommended expression | Reason |
|---|---|---|
| source imports lang3 API | direct project dependency | documents compile/runtime contract |
| library used only inside commons-text implementation | leave transitive unless policy requires management | project does not own direct API dependency |
| organization mandates approved family version | central management/platform/constraint in later chapters | govern version without fake code dependency |
| unwanted vulnerable transitive module | upgrade/constraint/exclusion only after impact analysis | removal/replacement can break upstream consumer |
4. Maven management and Gradle constraints solve related problems through different models
Maven’s nearest-definition mediation is a graph-path rule.
dependencyManagement can centrally control versions
when a dependency is encountered and takes precedence over ordinary
transitive mediation in the relevant project model. Gradle’s
dependency constraints participate in its resolution model and can
express required, preferred, rejected, or strict version intent.
Platforms/BOMs provide broader centralized governance.
Do not translate mechanically: a Maven managed version is not “the same feature” as every Gradle constraint. Compare the production invariant—what versions may be selected, where policy lives, and how the resolver reports the reason.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
</dependencies>
</dependencyManagement>
dependencies {
implementation("org.apache.commons:commons-text:1.10.0")
constraints {
implementation("org.apache.commons:commons-lang3:3.12.0") {
because("approved baseline for this service")
}
}
}
5. Public repositories versus controlled mirrors/proxies
Repository topology affects security, availability, performance, and
reproducibility. Maven can apply mirrors from
settings.xml; a common enterprise pattern mirrors all
remote requests through one repository manager. Gradle can
centralize dependency repositories in settings and restrict
repository content. The dedicated Nexus course will cover
repository-manager administration; here the concern is the
build-side contract.
| Topology | Strength | Risk/cost | Build-side control |
|---|---|---|---|
| direct public Central only | simple; fewer moving parts | internet availability and direct external trust | explicit single repository; immutable versions; verification |
| multiple public repositories | access to ecosystem-specific modules | repository sprawl, same-coordinate ambiguity, outage surface | minimum repositories + content filters |
| internal proxy/mirror | central cache/policy/audit/availability | manager becomes critical dependency | Maven mirror/settings or Gradle governed repositories; HA/backup handled elsewhere |
| internal hosted coordinates + public proxy | organization ownership + external access | dependency-confusion if namespace ownership unclear | content routing/filtering + reserved group namespaces |
6. Repository order is observable behavior, but the exact algorithms differ
Maven documents an effective repository order derived from effective
settings, local/parent/Super POMs, and dependency-path POMs, with
mirrors applied before download. Inspect
help:effective-settings and
help:effective-pom rather than guessing.
Gradle checks declared repositories during metadata lookup and, once it finds module metadata for a fixed version in a repository, uses that repository for that module’s artifacts. Dynamic version discovery can query multiple repositories to build the candidate set. Do not use generic advice like “first repository always wins” across all cases.
7. Worked decision — production JVM service in a controlled organization
| Decision | Chosen policy | Why | Verification |
|---|---|---|---|
| application dependencies | fixed releases; source-used APIs direct | reviewable graph intent | tree/insight in CI |
| dynamic versions | disallowed on protected release branch | prevents surprise selections | Gradle fail/locking policy; review Maven ranges |
| snapshots/changing | integration branch only; never release evidence | same coordinate may change | repository policy + graph record |
| external repositories | one governed proxy of approved upstreams | central policy, cache, audit | effective settings/repository model |
| internal group IDs | served only by controlled internal content route | reduces dependency-confusion exposure | content filter/repository-manager policy |
| upgrades | explicit dependency-update change with tests | makes change reviewable | before/after resolved graph + test/artifact evidence |
8. Common wrong approaches
-
Using
latest,+, ranges, or SNAPSHOTs on release branches without a locking/update policy. - Adding more repositories whenever resolution fails instead of fixing the missing coordinate or repository contract.
- Forcing a lower transitive version without checking the upstream library’s binary/runtime expectations.
- Assuming Maven nearest-wins and Gradle highest-wins are universal under all management, constraints, ranges, capabilities, and rules.
- Treating dependency cache contents as an authoritative artifact repository.
Knowledge check
What policy question distinguishes a fixed version from a dynamic selector?
Whether repository state is allowed to choose a different concrete version without a source/configuration change.
Why declare a library directly when your source imports its API even if it already arrives transitively?
It makes your actual compile/runtime contract explicit and prevents an upstream dependency-graph change from silently removing it.
Why is “put the internal repository first” insufficient defense against dependency confusion?
Different resolver behaviors, metadata lookups, and future configuration can still expose public sources. Use namespace ownership and content routing/filtering through governed repositories.
What should accompany a deliberate transitive downgrade?
Compatibility analysis and tests showing the upstream dependency still works with the selected lower version, plus an explicit reason/policy.
Does dependency locking solve mutable SNAPSHOT bytes?
No. Locking concrete version selection is useful for dynamic versions, but a changing module can republish different bytes under the same version identity.
Summary
Version syntax, transitivity, conflict policy, and repository topology are change-control decisions. Prefer explicit direct dependencies for APIs you own, immutable release requests, centrally governed version policy, minimal repository surfaces, and evidence that shows both the selected graph and the authority that supplied it.
Official references and version notes
These lessons were finalized against current primary documentation on 2026-08-23. Dependency metadata, plugin versions, repository policies, Gradle resolution behavior, and Maven/Gradle releases are version-sensitive. Verify the exact tool versions and repository policy used by your project and CI before applying production controls.
- Apache Maven — Releases History
- Maven — Introduction to the Dependency Mechanism
- Maven — Setting up Multiple Repositories
- Maven — Using Mirrors for Repositories
- Apache Maven Dependency Plugin
- Gradle 9.7.1 — Dependency Resolution
- Gradle 9.7.1 — Graph Resolution
- Gradle 9.7.1 — Viewing and Debugging Dependencies
- Gradle 9.7.1 — Declaring Versions and Ranges
- Gradle 9.7.1 — Dependency Caching
- Maven Central — commons-text 1.10.0 metadata
- Gradle 9.7.1 — Dependency Locking
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.