skip to content

What are Gradle plugin marker artifacts, and why are they needed for resolving plugins via the plugins {} block from a custom repository?

level: middleimportance: must knowfreq 55%

answer

  1. <id>:<id>.gradle.plugin:<version>
  2. marker = empty module depending on real jar
  3. id → coordinates bridge
  4. auto-generated with java-gradle-plugin + maven-publish
  5. resolved from pluginManagement.repositories

basics

~20 s

A plugin marker is a tiny published module whose coordinates are derived from the plugin id (id:id.gradle.plugin) and whose only job is to declare a dependency on the real plugin jar. It lets plugins {} resolve a plugin by id rather than by Maven coordinates.

solid answer

~40 s

When you apply a plugin with `plugins { id("com.acme.greeting") version "1.0" }`, Gradle has only the **id**, not Maven coordinates. To bridge that gap, each plugin publishes a **marker artifact**: a minimal POM/module at coordinates `com.acme.greeting:com.acme.greeting.gradle.plugin:1.0`. The marker has no code — it simply declares a runtime dependency on the actual implementation module (e.g. `com.acme:greeting-plugin:1.0`). Resolution flow: 1. Gradle maps the id to the marker coordinates `<id>:<id>.gradle.plugin`. 2. It looks the marker up in the repositories declared in `pluginManagement { repositories { … } }`. 3. The marker pulls in the real implementation jar transitively. When you use `java-gradle-plugin` together with `maven-publish`, the marker publications are generated automatically (one per declared plugin). The Plugin Portal works the same way; for a custom repo you must publish both the implementation and the markers.

code

kotlin · 19 lines
kotlin
plugins {
  `java-gradle-plugin`
  `maven-publish`
}

group = "com.acme"
version = "1.0"

gradlePlugin {
  plugins {
    create("greeting") {
      id = "com.acme.greeting"
      implementationClass = "com.acme.GreetingPlugin"
    }
  }
}
// Publishing now produces:
//   com.acme:greeting:1.0                                     (implementation)
//   com.acme.greeting:com.acme.greeting.gradle.plugin:1.0     (marker)

go deeper

for a junior

Know that plugins {} needs a marker to find a plugin by id from a repository.

for a middle

State the marker coordinate pattern and that java-gradle-plugin + maven-publish generates them automatically.

for a senior

Walk the full id→marker→implementation resolution chain and contrast with the legacy buildscript classpath path.

for a principal

Reason about repository topology for an internal plugin ecosystem: where markers live, mirroring, and consumption guarantees across teams.

## The id-to-coordinates problem The `plugins {}` DSL resolves plugins by **id**, e.g. `id("com.acme.greeting")`. But artifact repositories are keyed by Maven/Ivy **coordinates** (group:name:version). Something must translate an id into coordinates. That something is the **plugin marker artifact**. ## Marker coordinates For a plugin id `com.acme.greeting`, Gradle expects a marker module at: ``` group = com.acme.greeting name = com.acme.greeting.gradle.plugin version = <the version requested in plugins {}> ``` i.e. `<id>:<id>.gradle.plugin:<version>`. The marker is essentially an empty module: its POM declares a single dependency on the real implementation module that contains the compiled `Plugin<Project>` and the descriptor. ## Resolution sequence 1. `plugins { id("com.acme.greeting") version "1.0" }` is encountered. 2. Gradle searches `pluginManagement.repositories` for the marker `com.acme.greeting:com.acme.greeting.gradle.plugin:1.0`. 3. The marker's dependency resolves the real jar onto the build's plugin classpath. 4. Gradle reads `META-INF/gradle-plugins/com.acme.greeting.properties` from that jar to find `implementation-class` and applies it. ## Generating markers With `java-gradle-plugin` + `maven-publish` applied, Gradle automatically creates a `pluginMaven` publication for the implementation and one `<name>PluginMarkerMaven` publication per declared plugin. You then publish all of them: ```kotlin plugins { `java-gradle-plugin` `maven-publish` } ``` ## Why this matters in practice - A consumer who only knows the id can resolve the plugin without knowing internal coordinates. - One implementation jar can back several ids — each gets its own marker. - Forgetting to publish markers is the classic reason `plugins {}` fails with "Plugin not found" even though the implementation jar is in the repo (it would still work via `buildscript {}` classpath + `apply plugin`, which bypasses markers).

  • Why can apply plugin (legacy) resolve a plugin from buildscript classpath without a marker, but plugins {} cannot?
    The legacy path puts the jar directly on the buildscript classpath, so Gradle just needs the descriptor. The plugins {} block resolves by id from a repository, which requires the marker to translate the id into coordinates.
  • If one implementation jar declares two plugin ids, how many markers are published?
    Two — one marker module per declared id, each pointing at the same implementation module.

saying these in an interview costs you the question

  • Saying the marker contains the plugin code (it is empty; it only declares a dependency).
  • Forgetting that markers are needed specifically for plugins {} repository resolution, not for direct buildscript classpath use.
  • Inventing marker coordinates other than <id>:<id>.gradle.plugin.

context