Plugins Overview
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against Gradle 9.8.1 (the current release on 2026-10-16) with the embedded Kotlin 2.4.10 and Groovy 4.0.33. Every
example was run on JDK 26 through the Gradle wrapper on 2026-10-16, on macOS, with the configuration cache enabled
(org.gradle.configuration-cache=true in gradle.properties) unless the text says a run turned it off, and says whether the example is
configuration cache compatible. The Kotlin DSL is shown first and the Groovy DSL second wherever the syntax differs, and both versions were
run. Paths of the scratch workspace are shortened to <work>, and the task lines of long console outputs are trimmed where it is said. Gradle 8.14
was not available here: what differs in it is described from the official documentation in the last sections and is labelled "not run here".
Gradle itself knows almost nothing about Java, Kotlin, applications or publishing. Everything a build can do beyond running tasks in order comes from plugins: a plugin adds tasks, configurations, extensions, conventions and sometimes whole variants of the dependency model to the object it is applied to. Choosing, naming, locating and applying plugins is therefore the first thing every build script does, and this page is about that act. What happens inside a plugin is on Binary Plugins; how to share your own plugins between projects is on Convention Plugins & Sharing Build Logic; how to test and publish them is on Testing & Publishing Plugins. The vocabulary of builds, projects and tasks is on Core Concepts, and the two scripts that mention plugins first are described on Settings & Build Scripts.
Plugins in the three tools
Every build tool of this section has an extension mechanism, and they differ in how much of the tool itself is an extension.
| Ant | Maven | Gradle | |
|---|---|---|---|
Unit of extension |
a task or type, loaded from a JAR with |
a plugin made of goals (Mojos), identified by |
a plugin identified by a plugin id, with or without a version |
Core functionality |
the core tasks in |
almost nothing: compiling, testing and packaging are all plugins bound to lifecycle phases |
almost nothing: even |
How it is requested |
|
|
the |
Where it is found |
the class path ( |
the plugin repositories ( |
core: the distribution; others: the plugin repositories of |
What it contributes |
tasks and types |
goals, run in lifecycle phases |
tasks, extensions, configurations, conventions, variants |
The equivalent pages are Ant: Antlibs & Namespaces,
Ant: Writing Custom Tasks,
Maven: Plugins & Their Configuration,
Maven: Extensions and
Maven: Writing Plugins; the three philosophies side by side are on
Comparing Ant, Maven & Gradle. The most visible difference for a
reader coming from Maven is that a Gradle plugin is applied, not bound: applying java does not decide when anything runs, it only makes
tasks such as compileJava exist, and the task graph decides the order.
Three sources and three kinds of plugin
Gradle’s documentation distinguishes where a plugin comes from, and separately how a local plugin is written.
| Source | What it is | Example |
|---|---|---|
Core plugin |
part of the Gradle distribution, maintained by the Gradle team, always available, versioned with Gradle |
|
Community plugin |
published to the Gradle Plugin Portal (or another repository) by anyone, versioned on its own |
|
Local or custom plugin |
written for your own build or organization, kept in |
|
| Kind (of local plugin) | Where it lives | Typically | Remark |
|---|---|---|---|
Script plugin |
a |
a throw-away local helper |
convenient, but the official documentation says script plugins should be avoided |
Precompiled script plugin |
|
a convention plugin |
written in the build DSL, the plugin id comes from the file name |
Binary plugin |
a class implementing |
a shared, published plugin |
can be tested with TestKit and published to the Plugin Portal or a Maven repository |
The running example
The examples use the tidepool toolkit of this section: a tidepool-model library, a tidepool-engine library that reads station data with
Apache Commons CSV, and a tidepool-cli command line application built on picocli. The sources are the same as in the rest of the section;
only the build scripts are shown, because they are what plugins are about. The build starts with the smallest useful set of plugins: the core plugins
java-library and application, one community plugin from the Plugin Portal (io.github.ben-manes.versions, 0.66.0 on 2026-10-16, which adds a
dependencyUpdates report task) and one settings plugin (the Foojay toolchains resolver, 1.0.0).
pluginManagement {
repositories {
gradlePluginPortal()
mavenCentral()
}
}
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
}
rootProject.name = "tidepool"
include("tidepool-model", "tidepool-engine", "tidepool-cli")
pluginManagement {
repositories {
gradlePluginPortal()
mavenCentral()
}
}
plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0'
}
rootProject.name = 'tidepool'
include 'tidepool-model', 'tidepool-engine', 'tidepool-cli'
plugins {
id("io.github.ben-manes.versions") version "0.66.0" apply false
}
plugins {
id 'io.github.ben-manes.versions' version '0.66.0' apply false
}
plugins {
`java-library`
id("io.github.ben-manes.versions")
}
repositories {
mavenCentral()
}
dependencies {
api(project(":tidepool-model"))
implementation("org.apache.commons:commons-csv:1.14.1")
}
plugins {
id 'java-library'
id 'io.github.ben-manes.versions'
}
repositories {
mavenCentral()
}
dependencies {
api project(':tidepool-model')
implementation 'org.apache.commons:commons-csv:1.14.1'
}
tidepool-model/build.gradle(.kts) contains only (Kotlin) or java-library id 'java-library' (Groovy) in its plugins { } block, and
tidepool-cli/build.gradle(.kts) applies application, depends on project(":tidepool-engine") and info.picocli:picocli:4.7.7, and sets
application { mainClass = "io.tidepool.cli.Main" }. Building the whole thing with both DSLs gave (task lines trimmed; their order is not fixed):
...
BUILD SUCCESSFUL in 1s
9 actionable tasks: 9 executed
Configuration cache entry stored.
A second ./gradlew build reported 9 actionable tasks: 9 up-to-date and Configuration cache entry reused., so the build, including the
community plugin, is configuration cache compatible. The Plugin Portal page of the plugin carries the same statement as a "CC-compatible" badge
(and one for Isolated Projects, see
Configuration Cache & Isolated Projects); a
plugin that is not compatible makes the whole build incompatible, whatever your own scripts do. Running the application,
./gradlew -q :tidepool-cli:run --args="../data/stations.csv SF", printed San Francisco: -0,869 m in both DSLs (the decimal comma is the
default locale of the machine that ran it).
The sections below take this build apart: which plugins are core, what the Portal is, what plugins { } accepts, what pluginManagement { } adds,
and the older ways.
Core plugins
A core plugin is a plugin of the distribution. It has a short id (java, application) that is a shorthand for the id in the org.gradle
namespace (org.gradle.java), and Gradle never downloads it. Because it is versioned with Gradle, it cannot carry a version of its own, and
Gradle says so:
* What went wrong:
Error resolving plugin [id: 'java-library', version: '1.0']
> Plugin 'java-library' is a core Gradle plugin, which cannot be specified with a version number. Such plugins are versioned as part of Gradle. Please remove the version number from the declaration.
The same error text appeared for id("java-library") version "1.0" in the Kotlin DSL and id 'java-library' version '1.0' in the Groovy DSL.
The reverse mistake, apply false on a core plugin in the root script (a habit copied from community plugins), is also rejected:
* What went wrong:
Error resolving plugin [id: 'org.gradle.java-library', apply: false]
> Plugin 'org.gradle.java-library' is a core Gradle plugin, which is already on the classpath. Requesting it with the 'apply false' option is a no-op.
The Groovy DSL printed the same message with the id shown as java-library. The second message also explains why core plugins need no
repository: they are on the Gradle classpath from the start.
The plugin reference of the user manual lists the core plugins by purpose. The table groups the 42 ids that apply to a project; each id was
applied to a project of its own in a scratch build (one plugins { id("...") } per project, ./gradlew --continue help) and every one resolved on
Gradle 9.8.1 on macOS. That a plugin applies does not mean it is useful on the machine: the native ones need a C++ or Swift toolchain to build
anything.
| Purpose | Plugin ids |
|---|---|
JVM languages and frameworks |
|
Native languages |
|
Packaging and distribution |
|
Code analysis |
|
IDE integration |
|
Utility |
|
Besides these, the reference lists jvm-toolchain-management, which is a settings plugin that configures where JDKs are downloaded from (the
Foojay plugin of the example builds on it). Several plugins on the list apply others: java-library and application both apply java, and
the lifecycle tasks clean, assemble, check and build come from the lifecycle plugin that base builds on. The next section shows how to see
the complete result of applying a few plugins, which is much longer than the plugins named in the script. The documentation of each plugin has its own page: the Java plugins are on
Java Projects, testing and reports on
Testing JVM Projects, maven-publish and signing on
Publishing, java-gradle-plugin on
Binary Plugins, and build-init and wrapper on
Installation and
Gradle Wrapper.
Core plugins also leave the distribution over time. The maven plugin and uploadArchives were removed in Gradle 7.0, and FindBugs, JDepend, the
build announcements plugins, the build comparison plugin and the JavaScript plugins were removed in 6.0; the replacements (SpotBugs, maven-publish)
are community plugins or newer core plugins. The table at the end of the page lists the plugins that Gradle in Action (written for Gradle 1.7)
uses and that no longer exist.
Seeing what a project has applied
There is no built-in task that prints "the plugins of this project", because the answer is a list of class names that is rarely what you want. Three
views are available without writing code. The tasks a plugin adds appear in ./gradlew :tidepool-engine:tasks --all, and
./gradlew help --task dependencyUpdates prints a line such as registered by plugin 'io.github.ben-manes.versions'. The classpath that carries the community plugins of a project is
shown by buildEnvironment, which the next section uses. To list the applied plugin classes, a few lines at the end of the build script do it. The
first version, at the bottom of tidepool-engine/build.gradle.kts, is a configuration cache trap that the next paragraph explains:
val applied = plugins.map { it.javaClass.name.removeSuffix("_Decorated") }.sorted()
tasks.register("appliedPlugins") {
doLast {
applied.forEach { println(it) }
}
}
Plugins applied after the line that computes the list are not in it, so this goes last in the script. With the configuration cache on, the first run printed the list and then failed while storing the entry, and the next run failed the same way:
* What went wrong:
Configuration cache problems found in this build.
1 problem was found storing the configuration cache.
- Task `:tidepool-engine:appliedPlugins` of type `org.gradle.api.DefaultTask`: cannot serialize Gradle script object references as these are not supported with the configuration cache.
See https://docs.gradle.org/9.8.1/userguide/configuration_cache_requirements.html#config_cache:requirements:disallowed_types
applied is a property of the compiled Kotlin script object, and the task action refers to it, so the task would have to drag the script with it. The
fix is to copy the value into a local variable inside the task configuration block, which the action captures instead (the Groovy version has the same
shape with def names = applied):
val applied = plugins.map { it.javaClass.name.removeSuffix("_Decorated") }.sorted()
tasks.register("appliedPlugins") {
val names = applied
doLast {
names.forEach { println(it) }
}
}
def applied = plugins.collect { it.class.name.replace('_Decorated', '') }.sort()
tasks.register('appliedPlugins') {
def names = applied
doLast {
names.each { println it }
}
}
./gradlew -q :tidepool-engine:appliedPlugins printed, for the Kotlin DSL (the Groovy DSL printed the same list without the
KotlinScriptBasePlugin line):
com.github.benmanes.gradle.versions.VersionsPlugin
org.gradle.api.plugins.BasePlugin
org.gradle.api.plugins.HelpTasksPlugin
org.gradle.api.plugins.JavaBasePlugin
org.gradle.api.plugins.JavaLibraryPlugin
org.gradle.api.plugins.JavaPlugin
org.gradle.api.plugins.JvmEcosystemPlugin
org.gradle.api.plugins.JvmTestSuitePlugin
org.gradle.api.plugins.JvmToolchainsPlugin
org.gradle.api.plugins.ReportingBasePlugin
org.gradle.api.plugins.SoftwareReportingTasksPlugin
org.gradle.buildinit.plugins.BuildInitPlugin
org.gradle.buildinit.plugins.WrapperPlugin
org.gradle.kotlin.dsl.provider.plugins.KotlinScriptBasePlugin
org.gradle.language.base.plugins.LifecycleBasePlugin
org.gradle.testing.base.plugins.TestSuiteBasePlugin
Two plugins were named in the script (java-library and the community plugin); fourteen more came with them. Both runs of the corrected task were
configuration cache compatible: the second run reported Configuration cache entry reused. Gradle adds the _Decorated suffix to the class names of
plugins it instantiates, which the script strips. The rules for what a task action may capture are on
Writing Custom Tasks and
Lazy Configuration & Providers.
Community plugins and the Gradle Plugin Portal
A community plugin is any non-core plugin that a repository serves. The usual repository is the Gradle Plugin Portal, https://plugins.gradle.org/,
a searchable catalogue of plugins with one page per plugin and version. A plugin page has everything needed to use the plugin: the id, the author, the
source link, the versions with their release dates, the Gradle features the plugin declares compatibility with (configuration cache, Isolated
Projects), and ready-made snippets for the plugins { } block, for a buildSrc/convention-plugin dependency and for the legacy buildscript form.
The Portal is also a Maven-layout repository at https://plugins.gradle.org/m2/, which is what the gradlePluginPortal() function of a repository
block means, and it can be mirrored by an artifact repository manager (the Portal documentation says to mirror Central as well, because plugins have
dependencies there, and to allow plugins-artifacts.gradle.org, where the artifact downloads are redirected). Publishing to the Portal is described on
Testing & Publishing Plugins.
The plugin marker artifact
A plugins { } entry holds only an id and a version, which are not Maven coordinates. Gradle bridges the two with a plugin marker artifact whose
coordinates are derived from them: group plugin.id, artifact plugin.id.gradle.plugin, version plugin.version. The marker is an empty POM whose
only content is a dependency on the real implementation. The one for the plugin of the example, fetched from the Portal:
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>io.github.ben-manes.versions</groupId>
<artifactId>io.github.ben-manes.versions.gradle.plugin</artifactId>
<version>0.66.0</version>
<packaging>pom</packaging>
<dependencies>
<dependency>
<groupId>io.github.ben-manes</groupId>
<artifactId>gradle-versions-plugin</artifactId>
<version>0.66.0</version>
</dependency>
</dependencies>
</project>
Once resolved, the plugin is an ordinary dependency of the build script, which buildEnvironment shows. For the root project of the example (the
output is cut after the first dependency; it continues with Moshi, Okio and Kotlin libraries, one of which Gradle replaced by the Kotlin version it
embeds):
classpath
+--- io.github.ben-manes.versions:io.github.ben-manes.versions.gradle.plugin:0.66.0
| \--- io.github.ben-manes:gradle-versions-plugin:0.66.0
| \--- com.squareup.moshi:moshi-kotlin:1.15.2
...
buildEnvironment for :tidepool-engine printed No dependencies under classpath: the engine project did not declare the plugin version, it only
asked for the plugin that the root project had already put on the shared build script classpath. That sharing is what apply false in the root script
is for, and it is also the reason for one of the errors below. Because the implementation is a normal dependency, it is cached in the Gradle User Home like
any other (see Directories & Logging), it follows the repositories
declared for plugins, and for a repository that does not carry the plugin the build fails with the "not found" error shown below.
Choosing a community plugin
The Portal accepts any plugin that passes its checks, so a plugin on it is available, not endorsed. Before adding one it is worth checking, on its Portal page and repository, that:
-
it is maintained and has a release for the Gradle you run (the example plugin published 0.66.0 on 2026-10-12, one week before the Gradle 9.8.1 run of this page); plugins built for Gradle 8 can break on Gradle 9, which removed APIs that 8.x deprecated;
-
it declares configuration cache support, which the Portal shows as a badge (the plugins you apply are part of the cacheability of the build);
-
its id is the current one. Plugin ids move: asking for the old id of the example plugin,
com.github.ben-manes.versions, still worked on 0.66.0 and printedThe com.github.ben-manes.versions plugin id is deprecated; apply io.github.ben-manes.versions instead.The sample on the Portal page of a plugin is always the current id; -
you can account for what it pulls in: it runs with the full power of your build, which is why supply-chain controls apply to plugins just as to libraries (see Supply-Chain Security for JVM Builds).
The user manual’s best practice adds that you should upgrade Gradle before the plugins and test compatibility of plugin upgrades in a separate job, and that you should never assume a plugin is applied before or after another: application order is deterministic but changes when a project is added or a convention plugin applies another plugin.
How a plugin request is resolved
When a plugins { } entry is processed, Gradle looks for the id in an order that the "not found" error spells out: the core plugins, then the plugins
defined by included builds (and buildSrc), then the plugin repositories.
A request for a plugin that a parent project or the settings has already put on the classpath is a separate case: the same version, or no version, reuses it, and a different version is an error (shown below).
The "not found" error lists the three sources. For an id that does not exist and has no version, the repositories line says why it could not look:
* What went wrong:
Plugin [id: 'tidepool.nope'] was not found in any of the following sources:
- Gradle Core Plugins (plugin is not in 'org.gradle' namespace)
- Included Builds (No included builds contain this plugin)
- Plugin Repositories (plugin dependency must include a version number for this source)
With a version the repositories are searched, and the message shows the marker coordinates that were looked up and the repositories that were consulted
(the example build had the Plugin Portal, printed as "Gradle Central Plugin Repository", and Maven Central, printed as "MavenRepo", in its
pluginManagement). It is the same text in both DSLs:
* What went wrong:
Plugin [id: 'tidepool.nope', version: '1.0'] was not found in any of the following sources:
- Gradle Core Plugins (plugin is not in 'org.gradle' namespace)
- Included Builds (No included builds contain this plugin)
- Plugin Repositories (could not resolve plugin artifact 'tidepool.nope:tidepool.nope.gradle.plugin:1.0')
Searched in the following repositories:
Gradle Central Plugin Repository
MavenRepo
The plugins block
The plugins { } block is the recommended way to apply a plugin: the user manual’s best practices say to always use it. Because the block can only
contain a restricted form, Gradle can read it before running the rest of the script and resolve the plugins early, and the Kotlin DSL can generate
type-safe accessors for what they add (see Kotlin DSL
and the description of the block on
Settings & Build Scripts). The permitted forms are:
| Entry | Meaning |
|---|---|
|
a core plugin, or a plugin already on the classpath, applied |
|
the Kotlin DSL accessor for a core plugin id with a short name; |
|
a community plugin of that version, resolved and applied; the method form |
|
resolved and put on the classpath, but not applied to this project |
|
the same plugin, in a project whose parent or settings has already made it available |
|
a plugin declared in a version catalog (below) |
The syntax is deliberately constrained. The id and the version must be constant string literals, so a variable, a property or a function call is
rejected; the block must be a top-level statement of the script, so an if or loop around it is rejected; and it is available only in the build scripts
and settings scripts of a build, not in script plugins or init scripts. The constraint is what makes the block "idempotent and side effect free",
and the escape for a version that has to come from a property is pluginManagement, below. These restrictions let Gradle optimise the loading and reuse of
plugin classes and tell editors what the plugins add to the script. The statement about position differs between the two
languages: in the Groovy DSL any other statement before plugins { } is a compile error (the exact message is on the
Settings & Build Scripts page), in the Kotlin DSL a group = "..."
before the block compiled and ran. Keep plugins { } first in both.
Nesting is rejected differently in each language. In the Kotlin DSL (if (true) { plugins { `java-library` } }):
* What went wrong:
The plugins {} block must not be used here. If you need to apply a plugin imperatively, please use apply<PluginType>() or apply(plugin = "id") instead.
In the Groovy DSL the same nesting is not even recognised as a block, and Groovy looks for an ordinary method called plugins:
* What went wrong:
A problem occurred evaluating project ':tidepool-model'.
> Could not find method plugins() for arguments [build_77rxkrtoz4kfjna62kzgf7ilf$_run_closure1@7c8d72ad] on project ':tidepool-model' of type org.gradle.api.Project.
(The closure name in the message is generated and differs on every run.)
Source: Gradle User Manual: The plugins block, Gradle User Manual: Limitations of the plugins DSL and Gradle User Manual: Apply Plugins Using the plugins Block
Versions, apply false and the root project
The version of a community plugin can be written in exactly one place per build, and there are two usual places. The one in the running example is the
root script: id("io.github.ben-manes.versions") version "0.66.0" apply false resolves the plugin, puts it on the classpath shared by all projects,
and does not apply it to the root project. A subproject then writes id("io.github.ben-manes.versions") with no version. Without apply false the plugin
would also be applied to the root project, which is what you want for a plugin that is meant for the root. The user manual presents the combination as the
preferred way to apply a plugin to some of the subprojects, and the alternative of allprojects { apply(plugin = "...") } or subprojects { } in the
root script as "Not Recommended"; the recommended alternative is a convention plugin
(Convention Plugins & Sharing Build Logic).
Three errors belong to this arrangement. Asking again for the plugin with a different version in a subproject fails, because the plugin is already on the classpath at the version the root requested:
* What went wrong:
Error resolving plugin [id: 'io.github.ben-manes.versions', version: '0.65.0']
> The request for this plugin could not be satisfied because the plugin is already on the classpath with a different version (0.66.0).
Asking for the same version in a subproject worked (the build ran). Asking for a plugin without a version that nothing has made available fails with the
"not found" message above. And a settings plugin written in a project’s plugins { } block fails at application time, because it targets the wrong
object:
* What went wrong:
An exception occurred applying plugin request [id: 'org.gradle.toolchains.foojay-resolver-convention', version: '1.0.0']
> Failed to apply plugin 'org.gradle.toolchains.foojay-resolver-convention'.
> Settings plugins must be applied in the settings script.
Version catalog aliases
When the build has a version catalog, a plugin can be declared there once and applied with alias. The file below, gradle/libs.versions.toml, is a
plain text file (the TOML syntax is described on
Version Catalogs):
[versions]
versions-plugin = "0.66.0"
[plugins]
versions = { id = "io.github.ben-manes.versions", version.ref = "versions-plugin" }
// build.gradle.kts
plugins {
alias(libs.plugins.versions) apply false
}
// tidepool-engine/build.gradle.kts
plugins {
`java-library`
alias(libs.plugins.versions)
}
// build.gradle
plugins {
alias(libs.plugins.versions) apply false
}
// tidepool-engine/build.gradle
plugins {
id 'java-library'
alias(libs.plugins.versions)
}
Both builds succeeded with the configuration cache on, and :tidepool-engine:tasks --all listed dependencyUpdates. The catalog moves both the plugin id
and its version out of the scripts, and a plugin added to the catalog gets an accessor that the IDE completes.
pluginManagement
pluginManagement { } in the settings file is the configuration point for everything about finding plugins: where the repositories are, which versions
plugins get, and how an id maps to an artifact. It must be the first block of the settings script (the earlier page shows the error for the wrong order).
Without it, plugins resolve from the Gradle Plugin Portal only; the example lists the Portal and Maven Central because some plugins and some of their
dependencies are published only to Central. The block also exists for init scripts, inside settingsEvaluated { } (not run here).
Plugin repositories
The repositories { } block of pluginManagement is searched in order, and a plugin is looked for in each repository until one has it. These repositories
are entirely separate from the repositories { } that supply a project’s libraries: the former serve plugins and the build script classpath, the latter
serve compile and runtime dependencies. A private Maven or Ivy repository is declared with maven { url = uri("...") } or ivy { ... }, and the
documented form that puts a local repository ahead of the Portal is:
pluginManagement {
repositories {
maven(url = file("./maven-repo"))
gradlePluginPortal()
ivy(url = file("./ivy-repo"))
}
}
(That block is the one printed in the user manual and was not run here; the real runs of this page used the Portal and Central.) A repository
that serves plugins must contain the marker artifacts as well as the implementations: a repository that merely mirrors a library does not satisfy
plugins { id("...") version "..." }. The buildscript { repositories { } } block of the legacy form is a separate list for that script.
Plugins and the Gradle Plugin Portal do not always agree about Maven Central. The implementation of the example plugin was not on Maven Central on 2026-10-16: when a resolution rule (below) pointed at its implementation coordinates and the only plugin repository was Maven Central, the build failed:
* What went wrong:
Plugin [id: 'io.github.ben-manes.versions', version: '0.66.0', artifact: 'io.github.ben-manes:gradle-versions-plugin:0.66.0', apply: false] was not found in any of the following sources:
- Gradle Core Plugins (plugin is not in 'org.gradle' namespace)
- Included Builds (No included builds contain this plugin)
- Plugin Repositories (could not resolve plugin artifact 'io.github.ben-manes:gradle-versions-plugin:0.66.0')
Plugin versions in one place
pluginManagement { plugins { } } gives an id a version for the whole build, so that the build scripts, wherever they are, write the id alone. It does
not have the constrained syntax of the build script block: the version can be computed, in particular read from gradle.properties, which makes it
possible to change a version with a -P option or in a single line. The example moves the version of the community plugin there, from a property
named versionsPluginVersion:
org.gradle.configuration-cache=true
versionsPluginVersion=0.66.0
pluginManagement {
val versionsPluginVersion = providers.gradleProperty("versionsPluginVersion").get()
repositories {
gradlePluginPortal()
mavenCentral()
}
plugins {
id("io.github.ben-manes.versions") version versionsPluginVersion
}
}
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
}
rootProject.name = "tidepool"
include("tidepool-model", "tidepool-engine", "tidepool-cli")
pluginManagement {
def versionsPluginVersion = providers.gradleProperty('versionsPluginVersion').get()
repositories {
gradlePluginPortal()
mavenCentral()
}
plugins {
id 'io.github.ben-manes.versions' version versionsPluginVersion
}
}
plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0'
}
rootProject.name = 'tidepool'
include 'tidepool-model', 'tidepool-engine', 'tidepool-cli'
The root script then declares id("io.github.ben-manes.versions") apply false (Groovy: id 'io.github.ben-manes.versions' apply false) with no version,
and the engine’s script is unchanged. Both builds ran with the configuration cache on, and :tidepool-engine:dependencyUpdates produced its report
in a second run that reused the cache entry. The version of the foojay settings plugin stays in the settings plugins { } block: the plugin versions
of pluginManagement apply to project build scripts. The one-line form people copied for years, val versionsPluginVersion: String by settings,
is deprecated by Gradle 9.6 (it printed the by settings delegate deprecation under --warning-mode all in a run of this page), which is why the example
uses providers.gradleProperty(...); see
Build Environment & Properties.
Plugin resolution rules
resolutionStrategy { eachPlugin { } } can change a plugin request before it is resolved: the requested id, version and artifact are visible as
requested, and useModule and useVersion override them. The documented use is a plugin that was published without plugin markers, so that
the id has to be mapped to an artifact by hand. The example makes the same plugin resolve by implementation coordinates instead of its marker
(the version being taken from the request, so that only requests that carry a version are rewritten):
pluginManagement {
repositories {
gradlePluginPortal()
}
resolutionStrategy {
eachPlugin {
if (requested.id.id == "io.github.ben-manes.versions" && requested.version != null) {
useModule("io.github.ben-manes:gradle-versions-plugin:${requested.version}")
}
}
}
}
rootProject.name = "tidepool"
include("tidepool-model", "tidepool-engine", "tidepool-cli")
pluginManagement {
repositories {
gradlePluginPortal()
}
resolutionStrategy {
eachPlugin {
if (requested.id.id == 'io.github.ben-manes.versions' && requested.version != null) {
useModule("io.github.ben-manes:gradle-versions-plugin:${requested.version}")
}
}
}
}
rootProject.name = 'tidepool'
include 'tidepool-model', 'tidepool-engine', 'tidepool-cli'
With --info the resolution of the root request printed the rewritten artifact, and the subproject request without a version was left alone:
Resolved plugin [id: 'io.github.ben-manes.versions', version: '0.66.0', artifact: 'io.github.ben-manes:gradle-versions-plugin:0.66.0', apply: false]
Resolved plugin [id: 'org.gradle.application']
Resolved plugin [id: 'org.gradle.java-library']
Resolved plugin [id: 'io.github.ben-manes.versions']
Resolved plugin [id: 'org.gradle.java-library']
The --info log is the quickest way to see what Gradle did with each request. Resolution rules are an escape hatch; when a plugin has markers, as nearly every
Portal plugin does, none is needed. The third thing pluginManagement accepts is includeBuild("build-logic"), which makes the plugins of an included
build, normally the organisation’s convention plugins, resolvable by id; it is the subject of
Convention Plugins & Sharing Build Logic (not run here)
and of Composite Builds.
The legacy way: apply and buildscript
Before the plugins { } block (introduced in Gradle 2.1, together with the Plugin Portal), a build listed the plugin as a dependency of the build
script and then applied it. The form still works and is documented as legacy. Here is the engine of the example written that way:
// build.gradle.kts
buildscript {
repositories {
gradlePluginPortal()
}
dependencies {
classpath("io.github.ben-manes:gradle-versions-plugin:0.66.0")
}
}
// tidepool-engine/build.gradle.kts
apply(plugin = "java-library")
apply(plugin = "io.github.ben-manes.versions")
repositories {
mavenCentral()
}
dependencies {
add("api", project(":tidepool-model"))
add("implementation", "org.apache.commons:commons-csv:1.14.1")
}
// build.gradle
buildscript {
repositories {
gradlePluginPortal()
}
dependencies {
classpath 'io.github.ben-manes:gradle-versions-plugin:0.66.0'
}
}
// tidepool-engine/build.gradle
apply plugin: 'java-library'
apply plugin: 'io.github.ben-manes.versions'
repositories {
mavenCentral()
}
dependencies {
api project(':tidepool-model')
implementation 'org.apache.commons:commons-csv:1.14.1'
}
Both versions built :tidepool-engine and listed dependencyUpdates. The differences from the plugins { } block are the point of avoiding it:
-
the coordinates are the implementation’s Maven coordinates, not the id, and you have to know them (the marker artifact exists so that you do not);
-
the repository is declared in the script itself, so
pluginManagementrepositories do not apply; -
the plugin and its dependencies are added to the classpath of the script as ordinary dependencies, so nothing checks that two scripts ask for the same version of the plugin, which the plugin DSL does (the "already on the classpath" error above);
-
the Kotlin DSL cannot generate type-safe accessors for a plugin applied this way, so
api(...)andimplementation(...)are not available and the example has to useadd("api", ...). The effect onjava { }and ondependencies { }is demonstrated on Settings & Build Scripts; -
the Groovy DSL is unaffected because it resolves everything dynamically.
The legacy form is still the right tool in two situations: a library needed by the build script itself (a YAML or JSON parser used in the script
body), which belongs in buildscript { dependencies { classpath(...) } }, and a script plugin applied with apply(from = "other.gradle.kts"), where
a plugins { } block is not allowed. Note that apply(plugin = "...") also takes a class, apply<JavaPlugin>() in the Kotlin DSL and
apply plugin: JavaPlugin in Groovy.
Project, settings and init plugins
A plugin is a class with one method, apply(target), and the type of target decides where it can be applied. Gradle has three scopes:
| Interface | Scope | Applied in | Used to |
|---|---|---|---|
|
one project |
|
register tasks, apply other plugins, configure the project |
|
the whole build |
|
include projects, configure plugin management, dependency resolution, the build cache, toolchain download repositories |
|
the build invocation (and, from |
an init script |
inject repositories or enterprise settings into all builds on a machine |
For a precompiled script plugin the scope comes from the file name: name.gradle.kts is a project plugin, name.settings.gradle.kts is a settings plugin
and name.init.gradle.kts is an init plugin (and the same with .gradle for the Groovy DSL). A file called name.gradle.kts is a project plugin even if a
settings script applies it. The settings plugin of the running example, the Foojay resolver, was shown in the settings plugins { } block and its
failure when applied to a project above. See
Settings & Build Scripts for what a settings script can do.
An init script can apply a plugin to every project of every build it is used with, which is how an organisation enforces a convention or a report
without touching the repositories. The example applies the core project-report plugin, whose projectReport task writes reports about the project,
to all projects, from a file that is not part of the project:
allprojects {
apply(plugin = "project-report")
}
allprojects {
apply plugin: 'project-report'
}
Passed with -I, in both DSLs and with the configuration cache on, ./gradlew -q -I reports.init.gradle.kts :tidepool-model:tasks --group=reporting printed:
------------------------------------------------------------
Tasks runnable from project ':tidepool-model'
------------------------------------------------------------
Reporting tasks
---------------
projectReport - Generates a report about your project.
(the Groovy script printed the same). Without -I the same command printed No tasks for the group. The -I option is a one-off; a script in
init.d of the Gradle User Home applies to every build of the user. The location and the init script lifecycle are on
Init Scripts & the Tooling API and
Directories & Logging.
Local plugins
A plugin does not have to be published. The directory buildSrc, next to settings.gradle(.kts), is built before the rest of the build and its
output goes on the classpath of every build script; a precompiled script plugin placed there is available by id. The example adds a tiny plugin,
tidepool.stamp, which registers a stamp task in the tidepool group. It is a script in the build DSL; the file name is the plugin id.
plugins {
`kotlin-dsl`
}
repositories {
gradlePluginPortal()
}
plugins {
base
}
tasks.register("stamp") {
group = "tidepool"
description = "Prints the name of the project that applied this plugin."
val projectName = project.name
doLast {
println("stamped by $projectName")
}
}
plugins {
id 'groovy-gradle-plugin'
}
plugins {
id 'base'
}
tasks.register('stamp') {
group = 'tidepool'
description = 'Prints the name of the project that applied this plugin.'
def projectName = project.name
doLast {
println "stamped by ${projectName}"
}
}
tidepool-model/build.gradle(.kts) then applies it by id:
plugins {
`java-library`
id("tidepool.stamp")
}
./gradlew -q :tidepool-model:stamp printed stamped by tidepool-model in both DSLs, and :tidepool-model:tasks --group=tidepool listed stamp under
"Tidepool tasks". The configuration cache was on and the second run reused the entry, so the plugin is compatible: it copies project.name into a local
value at configuration time and not inside doLast, which is the pattern of the previous sections. buildSrc is a separate build with its own repositories. The Kotlin
variant needs one even though the kotlin-dsl plugin id resolves without it, because the plugin compiles the scripts against Kotlin libraries: without the
repositories { gradlePluginPortal() } block of the example, the run failed with
* What went wrong:
Execution failed for task ':buildSrc:generateExternalPluginSpecBuilders' (registered by plugin class 'org.gradle.kotlin.dsl.plugins.precompiled.PrecompiledScriptPlugins').
> Error while evaluating property 'pluginEntries' of task ':buildSrc:generateExternalPluginSpecBuilders'.
> Could not resolve all files for configuration ':buildSrc:compileClasspath'.
> Cannot resolve external dependency org.jetbrains.kotlin:kotlin-stdlib:2.4.10 because no repositories are defined.
Required by:
project ':buildSrc'
(the output continued with the same message for kotlin-reflect). The Groovy variant, groovy-gradle-plugin, ran without a repository block. Two limitations of the plugin id are documented: it cannot start with org.gradle and it cannot equal a core plugin id.
buildSrc is the simplest place for build logic and has a cost: any change in it makes the entire build out of date. An included build, for example
build-logic, added with pluginManagement { includeBuild("build-logic") }, holds the same precompiled script plugins with fewer invalidations, and the
user manual’s best practices name it as the preferred location of build logic, in particular of convention plugins. Standalone binary plugins, written as classes, are on
Binary Plugins. Both are covered on
Convention Plugins & Sharing Build Logic.
The plugin reference and where to read on
The user manual has a reference page that lists each core plugin with a one-line description and a link, grouped as in the table above; the Gradle DSL
reference documents the extension and task types a plugin adds, and the Javadoc documents the plugin classes. The sibling pages of this section that
describe particular plugins are listed in the previous sections. For a community plugin the documentation is the Portal page and the plugin’s own
repository, and the plugins { } snippet on its Portal page is the authoritative id and version to copy.
Gradle 8.14 and earlier releases
The plugin DSL is old and stable, so most of this page applies unchanged to Gradle 8.14, the last 8.x minor (not run here: 8.14 was not available). From
the release notes of the versions that introduced each feature: the plugins { } block and the Plugin Portal arrived in Gradle 2.1; the
pluginManagement { } block and plugin resolution rules in 3.5 (replacing pluginRepositories in the settings file); and
pluginManagement { plugins { } }, with versions read from gradle.properties, in 5.6. Plugin version catalog aliases came with version catalogs
(stable in 7.4). What Gradle 9.0 changed for plugins is mostly about the plugins themselves: it removed APIs that 8.x had deprecated (among them
conventions, Project.exec/javaexec and the "name"() task syntax), runs on JVM 17 or later, embeds Kotlin 2.2 and Groovy 4, and requires Kotlin Gradle plugin
2.0.0 or newer. A community plugin that has not been updated since 8.x can therefore fail on 9.x with messages that point at the plugin’s code, and
the remedy is to upgrade the plugin (the second reason to prefer plugins that declare their compatibility), or to replace it. The by settings,
by project and by extra property delegates that many pluginManagement examples used are deprecated from 9.6 (see above). The matrix of what
runs where is on
Versions, Compatibility & Release History and the
upgrade path on Upgrading Gradle.
Where Gradle in Action is outdated
Gradle in Action (Muschko, Manning, 2014) was written for Gradle 1.7, before the Plugin Portal existed, and its plugin chapters reflect that. Current status of the statements about plugins, from the official documentation:
| Book statement or practice | Current status | Source |
|---|---|---|
plugins are applied with |
the |
|
the |
removed in Gradle 7.0; use |
|
the |
deprecated in 3.0 and removed in 4.0 (Gretty is a third-party alternative) |
|
FindBugs, JDepend, and the build announcements plugins |
deprecated in 5.0 and removed in 6.0 (SpotBugs is third party) |
|
the build comparison plugin and the JavaScript plugins (rhino, envjs, coffeescript, jshint) |
removed in 6.0 |
|
the |
the legacy Sonar plugin was removed in 3.0; use the SonarQube plugin |
|
a Cobertura plugin for coverage |
the Cobertura plugin was always third party; JaCoCo is the core plugin |
|
|
convention plugins in |
|
the Groovy DSL only |
Kotlin DSL 1.0 in 5.0, and the default for |
|
JCenter and Bintray as the repository for plugins and libraries |
|
|
plugin tests with |
TestKit |
Where to go next
-
Binary Plugins: writing a
Plugin<Project>class, thejava-gradle-pluginplugin and the tidepooltidepool.station-checkplugin. -
Convention Plugins & Sharing Build Logic:
buildSrc,build-logicand convention plugins versus cross-project configuration. -
Testing & Publishing Plugins: TestKit and publishing to the Plugin Portal.
-
Multi Project Builds and Composite Builds: where plugins are applied across projects and builds.
-
Kotlin DSL and Groovy DSL & Migrating to Kotlin: type-safe accessors and the two languages.
-
Tasks and Build Lifecycle: what the tasks a plugin adds are made of, and when plugin application happens.
-
Gradle for the reading path of the section.