skip to content

A team has several independent repos and wants one source of truth for dependency versions. Compare a checked-in shared `libs.versions.toml` versus a published version catalog, and when you'd pick each.

level: seniorimportance: should knowfreq 35%

answer

  1. within build = TOML; across builds = share strategy
  2. vendored copy → drift, no infra
  3. published catalog → versioned artifact, governance
  4. upgrade = bump one coordinate
  5. catalog declares, platform enforces

basics

~20 s

A copied/checked-in TOML is simple but drifts across repos. A published catalog (version-catalog plugin) is a versioned artifact imported with from("g:a:v"), giving one governed source and controlled upgrades — at the cost of publishing infrastructure.

solid answer

~50 s

Within one build, `gradle/libs.versions.toml` is the source of truth. Across *independent* repos there is no single file, so you choose how to share. Option A: keep a TOML and copy/vendor it (git submodule, sync script, or include the file path). Cheap and needs no infra, but copies **drift** and updates are manual. Option B: publish the catalog with the `version-catalog` plugin and import it via `from("com.acme:catalog:1.0")`. Now versions are a **versioned artifact** in your repo manager: one owner, an auditable history, and consumers upgrade by bumping a coordinate — a controlled, reviewable rollout. The trade-off is real publishing/repository infrastructure and a release cadence for the catalog. Pick A for a small monorepo-adjacent setup or early-stage teams; pick B once you have many repos, a platform team, and need governance. Either way, a catalog only *declares* versions — to *enforce* them transitively you add a published platform/BOM alongside it.

code

kotlin · 7 lines
kotlin
// Consumer pinning a governed, versioned catalog — upgrades are a one-line bump
dependencyResolutionManagement {
    repositories { maven { url = uri("https://repo.acme.com") } }
    versionCatalogs {
        create("libs") { from("com.acme:catalog:1.4") } // bump to 1.5 to roll forward
    }
}

go deeper

for a junior

Know a catalog is shared within a build and there are ways to share across repos.

for a middle

Contrast a copied TOML with a published catalog at a basic level.

for a senior

Make the trade-off explicit (drift vs infra, rollout, audit) and separate declaration from enforcement.

for a principal

Define org strategy: published catalog + platform, ownership, release cadence, and staged rollout governance.

## The core distinction A version catalog is a *sharing within a build* mechanism. Sharing *across builds* (independent repos) requires either copying the declarations or turning them into an artifact. The question is really about distribution and governance. ## Option A — checked-in / vendored TOML You maintain one canonical `libs.versions.toml` and get it into each repo via: - a **git submodule** or subtree pointing at a shared file, then `from(files("shared/libs.versions.toml"))`; - a **sync script/CI job** that copies the file; or - simply re-using the convention file per repo and copy-pasting. Pros: zero publishing infrastructure, instant edits, trivially diffable. Cons: copies **drift** between repos, upgrades are manual and easy to forget, and there is no single audited version of "the versions" — the source of truth is a process, not an artifact. ## Option B — published version catalog Produce the catalog with the `version-catalog` plugin and publish via `maven-publish`; consumers import: ```kotlin dependencyResolutionManagement { repositories { maven { url = uri("https://repo.acme.com") } } versionCatalogs { create("libs") { from("com.acme:catalog:1.4") } } } ``` Pros: the catalog is a **versioned, immutable artifact**; ownership is explicit; upgrades are a one-line coordinate bump that goes through normal review; you can stage rollouts (some repos on 1.4, others on 1.5). Cons: you need a repository manager, a publishing pipeline, and a release process for the catalog — overhead that only pays off at scale. ## Decision guide | Factor | Vendored TOML | Published catalog | |---|---|---| | Infra needed | none | repo manager + publish pipeline | | Drift risk | high | low (single artifact) | | Upgrade rollout | manual per repo | bump one coordinate | | Auditability | process-based | artifact version history | | Best for | few repos, early stage | many repos, platform team | ## Important caveat Neither option *enforces* versions. A catalog supplies coordinates that modules opt into via accessors; it never overrides a transitive version. To make versions binding across the dependency graph, publish a **platform** (`java-platform`/BOM) and have consumers `implementation(platform(libs.myPlatform))` — a complementary, separate mechanism. A mature setup often ships *both*: a published catalog for ergonomic declarations and a published platform for enforcement. ## What good answers include Name the drift/governance trade-off, mention `from("g:a:v")` and settings-level repositories for the published path, and explicitly separate *declaration* (catalog) from *enforcement* (platform).

  • How do consumers upgrade to new versions with the published-catalog approach?
    They bump the catalog coordinate (e.g. `1.4` → `1.5`) in `settings.gradle.kts`. It's a reviewable one-line change, and you can stage the rollout repo by repo.
  • Does sharing a catalog guarantee every repo resolves the same transitive versions?
    No. A catalog only provides coordinates you opt into; it does not constrain transitive resolution. For binding enforcement publish a platform/BOM and depend on it with `platform(...)` — a separate mechanism that complements the catalog.
  • What's the main downside of vendoring a shared TOML by copy or submodule?
    Drift: copies fall out of sync and upgrades are manual per repo, so there's no single audited 'current versions' artifact — the source of truth becomes a process rather than a versioned thing.

saying these in an interview costs you the question

  • Claiming a shared catalog enforces versions across the dependency graph (it only declares).
  • Ignoring drift/governance and treating copied TOML as equivalent to a published artifact.
  • Forgetting that the published path needs settings-level repositories to resolve `from("g:a:v")`.

context