skip to content

How does Gradle map a catalog alias like `groovy-json` or `commons.lang3` to its generated accessor, and why does the mapping sometimes surprise people?

level: middleimportance: must knowfreq 55%

answer

  1. _ . all normalize to nesting
  2. groovy-json -> libs.groovy.json
  3. dash not a valid identifier
  4. leaf vs group collision
  5. must start with a letter

basics

~10 s

Dashes (and dots) in an alias become nested levels in the accessor: groovy-json becomes libs.groovy.json. The separators create sub-objects, so the alias a-b-c reads libs.a.b.c.

solid answer

~50 s

In a version catalog, alias names use `-`, `_`, or `.` as **sub-group separators**, and Gradle normalizes all three to a single dotted accessor path. So `groovy-json`, `groovy_json`, and `groovy.json` all generate the **same** accessor `libs.groovy.json`. This is why two aliases differing only by separator are treated as a collision. The surprise: an alias like `groovy-json` does NOT become a property literally named `groovy-json` (dashes aren't valid identifiers) — it nests under `libs.groovy` as `.json`. Conversely, you can't have both `groovy` (a leaf) and `groovy-json` (which needs `groovy` to be a group) — that's an aliasing conflict because `groovy` can't be both a terminal accessor and a parent object. Catalog alias names must start with a letter and contain only letters, digits and the separators. Understanding the mapping is essential to read completions like `libs.commons.lang3` and to avoid name clashes when designing aliases.

code

toml · 4 lines
toml
[libraries]
commons-lang3 = { module = "org.apache.commons:commons-lang3", version = "3.14.0" }
groovy-json   = { module = "org.codehaus.groovy:groovy-json", version = "3.0.5" }
# accessors: libs.commons.lang3  and  libs.groovy.json

go deeper

for a junior

Know the basic rule: a dash in the alias becomes a dot in the accessor (groovy-json -> libs.groovy.json).

for a middle

Explain that -, _ and . all normalize to nesting, so they can collide, and that segments must start with a letter.

for a senior

Articulate the leaf-vs-group conflict and how to design alias names to avoid clashes across a large catalog.

for a principal

Set catalog naming conventions (consistent dash separator, grouping scheme) as a team standard so accessors stay predictable across modules.

## The core rule: separators become nesting Version-catalog aliases are written with separators — `-` (hyphen, the convention), `_` (underscore), or `.` (dot). Gradle **canonicalizes** all three to the same thing and turns each separator into a level of nesting in the generated accessor. So every one of these aliases: ``` groovy-json groovy_json groovy.json ``` produces the **identical** accessor: ```kotlin libs.groovy.json ``` This is why mixing separators that produce the same path is a **collision** — Gradle will reject the catalog. ## Why dashes can't be literal property names `libs.groovy-json` would parse in Kotlin as `libs.groovy` minus `json` — a subtraction! Hyphens aren't legal identifier characters. So Gradle's accessor generator splits the alias on separators and emits a tree of typed getters: a `groovy` getter returning an object that has a `json` getter. The leaf returns the dependency `Provider`. ## Naming constraints - An alias must **start with a letter**. - It may contain letters, digits, and the three separators. - Each segment between separators becomes one accessor level. ## The classic conflict: leaf vs. group You cannot have both: ```toml [libraries] groovy = "org.codehaus.groovy:groovy:3.0.5" groovy-json = "org.codehaus.groovy:groovy-json:3.0.5" ``` Here `groovy` wants to be a terminal accessor (`libs.groovy`) returning a dependency, but `groovy-json` requires `libs.groovy` to be a **container** that has a `.json` child. An accessor can't be both a value and an object. Gradle rejects this. The fix is to rename, e.g. `groovy-core` and `groovy-json`, giving `libs.groovy.core` and `libs.groovy.json`. ## Reading completions When the IDE offers `libs.commons.lang3`, mentally reverse the mapping: the catalog alias is `commons-lang3` (or `commons.lang3`). The `version` namespace works the same way: a version ref `spring-boot` is read `libs.versions.spring.boot`. ```toml [versions] spring-boot = "3.2.0" [libraries] commons-lang3 = { module = "org.apache.commons:commons-lang3", version = "3.14.0" } ``` ```kotlin libs.commons.lang3 // the library libs.versions.spring.boot // Provider<String> = "3.2.0" ```

  • Why can't you declare both aliases `groovy` and `groovy-json` in the same catalog?
    `groovy` would be a terminal accessor returning a dependency, but `groovy-json` forces `libs.groovy` to be a container with a `.json` child. The same node can't be both a value and an object, so Gradle rejects it.
  • Do `my_lib`, `my-lib`, and `my.lib` produce different accessors?
    No — all three normalize to `libs.my.lib`. Declaring more than one of them is a collision.

saying these in an interview costs you the question

  • Saying the accessor is literally `libs."groovy-json"` or `libs.groovyJson` (camelCase) — it's the nested `libs.groovy.json`.
  • Thinking underscores and dots behave differently from dashes — they all normalize to the same dotted path.

context