Chapter 18Lesson 01~185 minutes

Gradle Plugins, Core Plugins, Community Plugins, Convention Plugins, and Plugin Management: Concepts, Architecture, and Mental Model

Treat Gradle plugins as executable build dependencies: understand where plugin code comes from, how it is resolved and applied, what model elements it contributes, and where reusable convention policy belongs.

Gradle 9.7.1Plugins DSLPlugin managementConvention pluginsSupply chain

Treat Gradle plugins as executable build dependencies: understand where plugin code comes from, how it is resolved and applied, what model elements it contributes, and where reusable convention policy belongs.

Learning objectives

  • Distinguish core, community, and repository-owned/local plugins by provenance and version policy.
  • Trace a plugin request through pluginManagement, marker/module resolution, application, configuration, and contributed model elements.
  • Explain why plugin repositories are a different trust boundary from ordinary dependency repositories.
  • Describe convention plugins and precompiled script plugins as reusable build policy rather than copied root-project scripting.
  • Separate plugin application from plugin configuration and explain why public Gradle APIs matter for upgrade compatibility.
  • Inspect plugin-provided tasks and build logic before changing the plugin graph.

1. The practical problem: plugins quietly become part of your build engine

Chapter 17 modeled Gradle as a truthful task graph. But many of those tasks, configurations, extensions, and lifecycle rules are not declared by Gradle core itself; they arrive when a plugin is applied. The Java plugin creates compilation and test-related model elements. A community formatter can add formatting tasks. A repository-owned convention plugin can apply both tools and set defaults across many subprojects.

That convenience creates a supply-chain boundary. Plugin code executes inside the Gradle process with the same filesystem, network, environment, and CI credential access that Gradle has. A successful build therefore proves only that the plugin ran successfully—not that its source, version, repository, or behavior was trustworthy.

Chapter invariant: every active plugin should have an intentional source, identity/version policy, application scope, and configuration owner. Shared policy should be reviewable as code, not hidden in workstation state.

2. Baseline and terminology before configuration

The mandatory path uses the already verified Gradle 9.7.1 Wrapper, JDK 21 to run Gradle, Java 17 for the small JVM projects, and an isolated GRADLE_USER_HOME. One public community plugin—com.diffplug.spotless 8.10.0—is used only as a controlled resolution example. All reusable Java conventions remain repository-owned source under build-logic/.

Term Meaning Version/source expectation
Core plugin Plugin distributed with Gradle, such as java, java-library, or application. Version follows the pinned Gradle distribution; do not add a separate plugin version in normal use.
Community plugin Third-party plugin resolved from a configured plugin repository or mapping. Pin an exact reviewed version and constrain repositories.
Convention plugin Repository/team plugin that applies/configures other plugins and shared defaults. Review its source and version/commit like production code.
Precompiled script plugin A .gradle.kts or .gradle source file compiled into a normal plugin; its ID derives from the filename. Useful for conventions; usually stored in buildSrc or an included build.
Plugin request An ID, optional version, and apply intent from a plugins {} block. Resolved before the plugin can be applied.
Plugin application Runs the plugin logic against a Project or Settings object. May add tasks/configurations/extensions and callbacks.
Plugin configuration Sets values on model elements supplied by the plugin. Should be deterministic, lazy where appropriate, and scoped to the intended projects.

3. Mental model: request → resolution → application → configuration

Plugin request and trust flow
flowchart TD
    S["settings.gradle.kts
pluginManagement"] R["Plugin request
ID + version"] M["Plugin repository / included build
marker + implementation"] C["Plugin code on
build classpath"] A["Apply to Project
or Settings"] E["Tasks + extensions
+ configurations"] G["Configured
task / model graph"] S --> R R --> M M --> C C --> A A --> E E --> G

The settings file defines the plugin-resolution boundary before project build scripts are evaluated. A community plugin request is mapped to a plugin marker/implementation from configured plugin repositories; an included build-logic build can contribute a local plugin instead. Only after code is available can Gradle apply it and expose tasks/extensions/configurations for subsequent configuration.

4. Core, community, and local plugins are different provenance classes

Plugin class Example Where code comes from What should reviewers prove
Core java Pinned Gradle distribution. Wrapper/distribution identity and Gradle upgrade compatibility.
Community com.diffplug.spotless Configured plugin repository; Plugin Portal in this lab. Exact plugin ID/version, repository policy, source/release provenance, compatibility.
Convention/local academy.java-conventions Reviewed source in included build-logic. Commit/source review, public API use, deterministic defaults, no secrets/environment coupling.
Published internal binary plugin e.g. company plugin ID Internal plugin repository. Published immutable version, repository controls, source/release provenance; administration belongs to the repository-manager course.

5. The plugins DSL declares identity; it is not arbitrary script code

A project build normally uses a declarative plugins {} block. Core plugins need no independent version because they ship with the Gradle distribution. Non-core plugin requests need a version unless a default version was established in plugin management or the plugin is supplied locally by an included plugin build.

plugins {
    java                                  // core plugin from Gradle 9.7.1
    id("com.diffplug.spotless") version "8.10.0" // community plugin
}

Pinning the Wrapper and pinning an external plugin solve different problems. The Wrapper selects the Gradle engine; the plugin version selects executable build logic loaded into that engine.

6. pluginManagement is an early settings-level resolution policy

pluginManagement {} belongs in settings.gradle(.kts) and is evaluated early enough to define plugin sources, default versions, resolution rules, and included plugin builds. It does not apply a project plugin merely because a version is listed.

pluginManagement {
    includeBuild("build-logic")
    repositories {
        gradlePluginPortal()
    }
    plugins {
        id("com.diffplug.spotless") version "8.10.0"
    }
}
Statement Changes Does not do
repositories { ... } Plugin resolution source set. Does not configure project dependency repositories.
plugins { id(...) version ... } Default version policy for later requests. Does not apply the plugin.
includeBuild("build-logic") Makes plugins produced by that included build available to plugin resolution. Does not automatically apply every plugin from that build.
resolutionStrategy { ... } Can rewrite/map plugin requests when an intentional compatibility policy requires it. Should not become opaque magic that hides origin/version.

7. Plugin repositories and dependency repositories are separate trust boundaries

A project can use repositories { mavenCentral() } for application libraries while pluginManagement.repositories governs plugin resolution. Tightening one boundary does not automatically tighten the other. This distinction matters in CI because plugin code executes during build configuration and execution, often before application dependencies are even compiled.

Security boundary: changing plugin repositories, resolution rules, or included plugin builds changes executable build code. Treat those diffs like dependency or CI-runtime changes, not cosmetic build-file edits.

8. Plugin IDs resolve through plugin metadata, not project dependency syntax

For a normal community-plugin request, Gradle uses the plugin ID/version to locate plugin marker metadata and then the implementation artifact. The Plugin Portal shows the marker coordinates for com.diffplug.spotless 8.10.0 and the dependency form used when a precompiled convention-plugin build needs that external plugin on its own build-logic classpath.

This is why copying a library repository declaration into dependencies {} is not a substitute for a correct plugin-resolution policy.

9. Convention plugins turn repeated policy into named, reviewable build logic

A convention plugin normally applies core or community plugins and sets organization/project defaults: Java toolchain, compiler encoding, test policy, dependency constraints, publication defaults, or task conventions. Gradle’s current guidance favors convention plugins over broad allprojects {}/subprojects {} blocks because the policy has a name, scope, implementation, and independent model boundary.

// build-logic/src/main/kotlin/academy.java-conventions.gradle.kts
plugins {
    java
}

java {
    toolchain.languageVersion.set(JavaLanguageVersion.of(17))
}

tasks.withType<JavaCompile>().configureEach {
    options.encoding = "UTF-8"
    options.release.set(17)
}

The filename produces the plugin ID academy.java-conventions. Applying that ID is clearer than copying the same Java configuration into every module.

10. buildSrc and an included build solve related problems with different boundaries

buildSrc/ is automatically discovered and is convenient for small builds/prototypes. An explicit included build such as build-logic/ is a complete Gradle build with a clearer independent boundary. Current Gradle build-structure guidance generally favors an included build for scalable shared build logic, while acknowledging that buildSrc remains useful in some cases.

Choice Strength Cost / review question
buildSrc Simple; automatically available. Changes can broadly invalidate the main build; classloader/model behavior is more implicit.
Included build-logic Explicit composite-build boundary; independently buildable; can be split into focused plugin projects. Requires explicit inclusion and its own build files/repositories.
Published binary convention plugin Can be reused across repositories without rebuilding from source each time. Requires release/version/repository governance beyond this local chapter.

11. Application and configuration are separate events

Applying java creates Java-related model elements. Configuring java { toolchain ... } changes those elements. A robust convention plugin should either apply the plugin whose extension/tasks it configures or react to that plugin deliberately; it should not assume a task/extension exists because another script “usually” applies it first.

That principle prevents order-sensitive build logic and connects directly to Chapter 16’s lazy configuration model.

12. Plugin compatibility depends on supported public APIs

A plugin compiled against Gradle’s documented public API has a much better upgrade story than one importing internal implementation types such as packages containing .internal.. Internal APIs can change without the compatibility guarantees expected for public API. During a Gradle upgrade, plugin and extension compatibility should be tested before changing the fleet-wide Wrapper.

13. Read-only inspection before adding or upgrading a plugin

Start with identity and model evidence from a trusted repository:

export GRADLE_USER_HOME="$PWD/.lab-gradle-home"
./gradlew --version
./gradlew projects
./gradlew tasks --all
./gradlew buildEnvironment

Then inspect settings.gradle(.kts), pluginManagement, root/subproject plugins {} blocks, and repository-owned build logic. tasks --all proves what tasks are currently visible; it does not by itself prove which repository supplied a plugin or whether the plugin is safe.

14. DevOps connection: plugins participate in CI trust before artifacts exist

A CI job may expose source code, repository tokens, signing material, proxy credentials, or cloud environment variables to the Gradle process. A plugin can theoretically access all of that if the process can. Production review therefore asks not only “what task does this plugin add?” but also “who publishes this code, which exact version did we resolve, through which repository policy, and where is its shared configuration reviewed?”

Lesson 2 makes those questions observable in a disposable multi-project build.

Knowledge check

Why does a core java plugin normally omit a version?

Does declaring a default version in pluginManagement.plugins apply that plugin?

Why is repositories { mavenCentral() } not enough to govern community plugins?

What is the main purpose of a convention plugin?

Why are org.gradle.*.internal.* APIs risky in plugin code?

Why should plugin provenance be reviewed like dependency provenance?

15. Bridge to the guided workflow

Next you will create a two-subproject JVM build, centralize a pinned community-plugin version in settings, put Java policy in an included build-logic convention plugin, and inspect the exact tasks/extensions each plugin contributes before running the build.

Official references and version notes

Version snapshot: Generated August 24, 2026 with Gradle 9.7.1, JDK 21 as the Gradle runtime, Java 17 as the course JVM target, and com.diffplug.spotless 8.10.0 as the one external community-plugin example. Re-check plugin versions and compatibility before adopting them in production.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.