skip to content

Inside a convention plugin in buildSrc, how do you add a dependency from the `libs` catalog, and how do `findLibrary`, `findBundle`, `findVersion`, and `findPlugin` differ?

level: middleimportance: must knowfreq 55%

answer

  1. VersionCatalogsExtension.named("libs")
  2. findLibrary -> single dep Provider
  3. findBundle -> list of deps
  4. findVersion -> VersionConstraint only
  5. findPlugin -> PluginDependency

basics

~10 s

Get VersionCatalogsExtension, call .named("libs"), then use findLibrary/findBundle/findVersion/findPlugin (each returns an Optional), .get() it, and pass libraries/bundles to dependencies.add(...).

solid answer

~30 s

You resolve the catalog programmatically: `extensions.getByType<VersionCatalogsExtension>().named("libs")` gives a `VersionCatalog`. From it: `findLibrary("alias")` returns `Optional<Provider<MinimalExternalModuleDependency>>` for a single library; `findBundle("name")` returns a provider for a *list* of dependencies; `findVersion("name")` returns `Optional<VersionConstraint>` (just the version string/constraint, no coordinates); `findPlugin("id")` returns `Optional<Provider<PluginDependency>>` used mainly to apply plugins by id/version. For libraries and bundles you typically do `dependencies.add("implementation", libs.findLibrary("x").get())`. Aliases use the raw catalog spelling with dashes (`"junit-jupiter"`), not the dotted accessor form. The `Optional` lets you fail soft or default when an alias is absent.

code

kotlin · 12 lines
kotlin
override fun apply(project: Project) = with(project) {
    val libs = extensions.getByType<VersionCatalogsExtension>().named("libs")

    libs.findLibrary("jackson-databind").ifPresent {
        dependencies.add("implementation", it)
    }
    libs.findBundle("testing").ifPresent {
        dependencies.add("testImplementation", it)
    }
    val kotlinVersion = libs.findVersion("kotlin").get().requiredVersion
    logger.lifecycle("Pinning kotlin to $kotlinVersion")
}

go deeper

for a junior

Know that you call findLibrary("alias").get() and pass it to dependencies.add("implementation", ...).

for a middle

Distinguish findLibrary/findBundle/findVersion/findPlugin and their return types, and handle the Optional.

for a senior

Discuss soft-failure with Optional in shared plugins, version-only lookups as a single source of truth, and why plugins{} alias sugar isn't available here.

for a principal

Define a team convention for which catalog aliases convention plugins may depend on, and how to keep that contract stable across many modules and teams.

## The entry point All programmatic catalog access goes through `VersionCatalogsExtension`, a Gradle-registered extension on `Project`: ```kotlin val catalogs = project.extensions.getByType<VersionCatalogsExtension>() val libs = catalogs.named("libs") // VersionCatalog ``` `named("libs")` must match the catalog name declared in settings (`libs` is the default for `gradle/libs.versions.toml`). ## The four lookup methods | Method | Returns | Use for | |---|---|---| | `findLibrary(alias)` | `Optional<Provider<MinimalExternalModuleDependency>>` | a single dependency coordinate (group:name:version) | | `findBundle(name)` | `Optional<Provider<ExternalModuleDependencyBundle>>` | a named group of libraries added together | | `findVersion(name)` | `Optional<VersionConstraint>` | just the version constraint, no coordinates — useful to set toolchain/plugin versions | | `findPlugin(id)` | `Optional<Provider<PluginDependency>>` | a plugin id+version, e.g. to apply by id | There are also `find*` siblings without the `find` returning non-Optional in some APIs, but the `find*` family is the safe, present-day choice. ## Adding dependencies ```kotlin libs.findLibrary("spring-boot-starter-web").ifPresent { dep -> project.dependencies.add("implementation", dep) } libs.findBundle("testing").ifPresent { bundle -> project.dependencies.add("testImplementation", bundle) } ``` Using `ifPresent` (or `.get()` when you know it must exist) handles the `Optional`. ## Versions and plugins `findVersion` returns only a `VersionConstraint` — you call `.get().requiredVersion` to get the string. This is handy when you want the catalog to be the single source of truth for, say, a tool version you pass elsewhere. `findPlugin` returns a `PluginDependency` provider; you usually read its `pluginId`/`version` or use it to drive `pluginManager.apply(...)`, since you generally cannot use the `plugins { }` block alias form from inside compiled plugin code. ## Alias name mangling recap The TOML alias `junit-jupiter` (or `junit.jupiter`) is normalized; in these `find*` calls you pass the original alias string, conventionally with dashes: `findLibrary("junit-jupiter")`. The dotted `libs.junit.jupiter` form is the *accessor* sugar, not the programmatic key.

  • How does `findVersion` differ from `findLibrary` in what it gives you?
    `findVersion` returns only a `VersionConstraint` (the version string/range), with no group or artifact name. `findLibrary` returns a full dependency provider (group:name:version) you can add to a configuration.
  • Why might you prefer `ifPresent` over `.get()` when reading an alias?
    `ifPresent` (or `orElseGet`) tolerates an absent alias without throwing, which is safer in a shared convention plugin applied to many modules where not every alias is guaranteed to exist.
  • Can you apply a plugin via the catalog inside a convention plugin?
    Not with the `plugins { alias(libs.plugins.x) }` block (that's script-only sugar). You read `findPlugin("id").get()` to get the `PluginDependency` (id + version) and drive application yourself or rely on the buildscript classpath.

saying these in an interview costs you the question

  • Using dotted accessor spelling (`findLibrary("junit.jupiter")` thinking it maps like the accessor) without realizing the raw alias string is what's passed.
  • Treating `findVersion` as if it returned coordinates — it only returns a version constraint.
  • Calling `.get()` blindly on aliases that may be absent in some consuming modules.

context