skip to content

A teammate writes `libs.findLibrary("junit.jupiter")` in a convention plugin and it returns an empty Optional, even though `libs.junit.jupiter` works in build.gradle.kts. What's going on and how do you fix it?

level: juniorimportance: should knowfreq 40%

answer

  1. accessor dots = navigation, not the key
  2. findLibrary takes raw alias (dashes)
  3. wrong key -> Optional.empty() silently
  4. orElseThrow to fail loud
  5. junit-jupiter not junit.jupiter

basics

~10 s

The programmatic lookup key is the original catalog alias (dashes), not the dotted accessor form. Use findLibrary("junit-jupiter"). The dots in libs.junit.jupiter are accessor navigation sugar, not the alias string.

solid answer

~30 s

In `gradle/libs.versions.toml` the alias is declared like `junit-jupiter = ...`. Gradle's type-safe accessor turns the `-` (and `.`) separators into navigable dots, so in a build script you write `libs.junit.jupiter`. But `findLibrary` takes the **raw alias name** — `"junit-jupiter"`. Passing `"junit.jupiter"` doesn't match the registered alias, so you get an empty `Optional`, which silently does nothing if you only `ifPresent`. The fix is to pass the dash form: `libs.findLibrary("junit-jupiter").get()`. As a guard, prefer `.orElseThrow { ... }` or log when absent during development so a typo'd alias fails loudly instead of silently skipping the dependency.

code

kotlin · 7 lines
kotlin
// Wrong: dotted form -> Optional.empty(), dependency silently skipped
libs.findLibrary("junit.jupiter").ifPresent { /* never runs */ }

// Right: raw alias with dashes, fail loudly if missing
val junit = libs.findLibrary("junit-jupiter")
    .orElseThrow { GradleException("alias 'junit-jupiter' not found in libs") }
dependencies.add("testImplementation", junit)

go deeper

for a junior

Know to pass the dash form "junit-jupiter" to findLibrary, and that dotted accessor paths are sugar.

for a middle

Explain the silent empty-Optional failure mode and use orElseThrow/get to fail fast on typos.

for a senior

Connect the silent-skip risk to shared plugins applied across many modules and prescribe a fail-loud convention.

for a principal

Consider tooling/lint or a small self-check that validates required aliases exist, so catalog drift can't silently degrade every module's build.

## Two spellings of the same alias A catalog alias such as `junit-jupiter` (or `commons-lang3`) has two faces: 1. **Accessor form** (build scripts): Gradle generates nested accessors, splitting on `-` and `.`, so `junit-jupiter` becomes `libs.junit.jupiter`. This gives IDE completion and grouping (`libs.androidx.compose.ui`). 2. **Programmatic form** (`findLibrary`): you pass the **original alias string**, conventionally with dashes — `findLibrary("junit-jupiter")`. The mistake is assuming the dotted accessor path *is* the lookup key. It isn't — the dots are generated navigation, not the registered name. ## Why the failure is sneaky `findLibrary` returns an `Optional`. A wrong key yields `Optional.empty()`. If your code is: ```kotlin libs.findLibrary("junit.jupiter").ifPresent { dependencies.add("testImplementation", it) } ``` then nothing is added and **no error is raised** — the build succeeds but the dependency is missing, surfacing later as a compile/test failure that looks unrelated. ## The fix and a defensive habit ```kotlin // Correct alias spelling val junit = libs.findLibrary("junit-jupiter") .orElseThrow { GradleException("Missing alias 'junit-jupiter' in libs catalog") } dependencies.add("testImplementation", junit) ``` Using `orElseThrow` (or at least logging) converts a silent miss into a clear failure. This matters most in shared convention plugins, where a typo affects every module that applies the plugin. ## Quick reference | TOML alias | Build-script accessor | findLibrary arg | |---|---|---| | `junit-jupiter` | `libs.junit.jupiter` | `"junit-jupiter"` | | `androidx-core-ktx` | `libs.androidx.core.ktx` | `"androidx-core-ktx"` | | `commons.lang3` | `libs.commons.lang3` | `"commons-lang3"` (normalized) |

  • Why is the empty-Optional failure mode dangerous in a shared convention plugin?
    It silently omits the dependency for every module applying the plugin, with no build error — the problem only surfaces later as confusing compile/test failures far from the typo.
  • How would you make such a typo fail fast?
    Use `orElseThrow { ... }` (or `get()` when the alias must exist) instead of `ifPresent`, so a missing alias throws a clear `GradleException` at configuration time.

The dotted accessor is like a file explorer's tree view of junit > jupiter; findLibrary wants the actual filename junit-jupiter. Typing the tree path as a filename finds nothing.

saying these in an interview costs you the question

  • Insisting the dotted accessor path is the literal alias string passed to findLibrary.
  • Defaulting to `ifPresent` everywhere, masking typo'd aliases as silent no-ops.
  • Assuming an empty Optional means the dependency doesn't exist in the repo, rather than a wrong lookup key.

context