How does Gradle resolve a plugin id declared in `plugins {}` to an actual artifact, and what role does `gradlePluginPortal()` / `pluginManagement` play?
answer
- id -> marker artifact <id>:<id>.gradle.plugin:<version>
- Repos come from pluginManagement in settings.gradle.kts
- Default repo = gradlePluginPortal()
- Plugin repos != dependency repos
- Core plugins / classpath plugins skip resolution
basics
~10 sWhen you declare id("...") version "...", Gradle looks for that plugin in the plugin repositories. By default it searches the Gradle Plugin Portal. You configure these repositories in the pluginManagement block of settings.gradle.kts.
solid answer
~40 sA `plugins {}` entry triggers **plugin resolution**: Gradle turns the id into a **plugin marker artifact** with coordinates `<id>:<id>.gradle.plugin:<version>`, which redirects to the real implementation jar. It searches the repositories declared in `pluginManagement { repositories {} }` in `settings.gradle.kts`; if none are declared, Gradle uses `gradlePluginPortal()` by default. The portal (plugins.gradle.org) hosts community plugins. Core plugins (`java`, `application`) and any plugin already on the classpath skip this resolution. You can add `mavenCentral()`, a corporate Maven repo, or `includeBuild` for composite builds. `pluginManagement` also lets you remap ids and centralize plugin versions via the `plugins {}` block inside it (`plugins { id("...") version "..." }` in settings). Resolution order follows the declared repository order.
code
kotlin · 12 lines// settings.gradle.kts
pluginManagement {
repositories {
gradlePluginPortal()
mavenCentral()
}
}
// build.gradle.kts
plugins {
id("com.github.johnrengelman.shadow") version "8.1.1" // resolved from a plugin repo
}go deeper
Knows plugins come from the Gradle Plugin Portal by default.
Explains marker artifacts, pluginManagement in settings, and that plugin repos differ from dependency repos.
Configures custom/corporate plugin repos, centralizes versions in pluginManagement, and uses composite builds for local plugins.
Designs resolution policy (mirrors, internal portal, repository order, supply-chain control) across many builds and reasons about reproducibility/security of the plugin classpath.
## What 'resolving a plugin' means When Gradle reads `id("org.jetbrains.kotlin.jvm") version "2.0.20"`, it must find the code that implements that plugin. This is **plugin resolution**, and it is distinct from normal **dependency resolution**. ## Step 1 — the plugin marker artifact Gradle maps the plugin id to a synthetic **marker artifact**: ``` org.jetbrains.kotlin.jvm -> org.jetbrains.kotlin.jvm:org.jetbrains.kotlin.jvm.gradle.plugin:2.0.20 ``` The marker is a tiny POM whose only job is to declare a dependency on the **real implementation jar** (`org.jetbrains.kotlin:kotlin-gradle-plugin`). This indirection is why you can apply a plugin by a short id without knowing its actual Maven coordinates. ## Step 2 — where Gradle looks: `pluginManagement` Plugin (marker) artifacts are fetched from repositories declared in **`settings.gradle.kts`**: ```kotlin pluginManagement { repositories { gradlePluginPortal() // plugins.gradle.org — community plugins mavenCentral() // some plugins publish here maven("https://corp.example.com/repo") // internal plugins } } ``` - If you declare **no** `pluginManagement.repositories`, Gradle defaults to **`gradlePluginPortal()`**. - These are **plugin** repositories, separate from `dependencyResolutionManagement { repositories {} }` used for normal dependencies. - Repositories are tried **in declared order**. ## What skips resolution - **Core plugins** (`java`, `java-library`, `application`, `base`) ship inside Gradle — no version, no repository lookup. - Any plugin **already on the classpath** (e.g. declared `apply false` in the root, or provided by `buildSrc`) is reused rather than re-resolved. ## Centralizing versions in `pluginManagement` You can pin community-plugin versions in `settings.gradle.kts` so subproject `plugins {}` blocks need no version: ```kotlin pluginManagement { plugins { id("org.jetbrains.kotlin.jvm") version "2.0.20" } } ``` Then `plugins { id("org.jetbrains.kotlin.jvm") }` (no version) works everywhere. (Version catalogs are the more common modern alternative.) ## Composite builds `includeBuild("my-plugin")` makes a locally built plugin available to `plugins {}` without publishing — useful for developing convention/custom plugins. ## Summary of the flow 1. `id + version` -> marker coordinates. 2. Search `pluginManagement` repos (default `gradlePluginPortal()`). 3. Marker -> implementation jar -> add to plugin classpath -> apply (unless `apply false`).
- What is a plugin marker artifact and why does it exist?It is a tiny POM at coordinates `<id>:<id>.gradle.plugin:<version>` that only declares a dependency on the real implementation jar. It lets you apply a plugin by a short id without knowing its actual Maven coordinates.
- Are `pluginManagement.repositories` the same as `dependencyResolutionManagement.repositories`?No. The former resolves plugin marker artifacts; the latter resolves your project's compile/runtime dependencies. They are configured separately in `settings.gradle.kts`.
The plugin id is a nickname; the marker artifact is a forwarding address that redirects the mail (the request) to the plugin's real street address (the implementation jar).
saying these in an interview costs you the question
- Putting plugin repositories in `dependencies`/project `repositories {}` instead of `pluginManagement`
- Not knowing `gradlePluginPortal()` is the default plugin repo
- Being unaware of the marker-artifact indirection
- Thinking core plugins are resolved from a repository
- Confusing plugin resolution with dependency resolution