Gradle Plugins, Core Plugins, Community Plugins, Convention Plugins, and Plugin Management: Guided Hands-On Workflow and Core Operations
Build a disposable multi-project Gradle scenario with core plugins, one pinned community plugin, and a repository-owned convention plugin in an included build; then prove every new task and configuration surface.
Build a disposable multi-project Gradle scenario with core plugins, one pinned community plugin, and a repository-owned convention plugin in an included build; then prove every new task and configuration surface.
Learning objectives
- Bootstrap a disposable multi-project Gradle build with the verified 9.7.1 Wrapper and isolated Gradle User Home.
-
Centralize an exact community-plugin version and repository under
pluginManagement. -
Create and apply a Kotlin precompiled convention plugin from an
included
build-logicbuild. - Apply core Java/application plugins and the pinned Spotless community plugin in intentionally scoped projects.
- Inspect plugin-provided tasks and configuration surfaces before running them.
- Prove the resulting build/test/formatting state and preserve resolution evidence.
1. Scenario: app + library + reviewed build logic
The lab has two production subprojects, app and
library. Both receive Java runtime/compiler conventions
from academy.java-conventions. Only
app uses the Spotless community plugin, demonstrating
that centralized version policy does not mean forced application
everywhere.
The public Plugin Portal is the only external plugin repository in the mandatory example. Application-library dependencies are unnecessary, keeping the resolution surface small.
2. Preflight: trusted Wrapper and isolated mutable state
mkdir gradle-plugin-lab
cd gradle-plugin-lab
export GRADLE_USER_HOME="$PWD/.lab-gradle-home"
# Copy the already verified Gradle 9.7.1 Wrapper files from Chapter 15.
chmod +x gradlew
./gradlew --version
java -version
mkdir -p app/src/main/java/dev/academy/app
mkdir -p library/src/main/java/dev/academy/lib
mkdir -p build-logic/src/main/kotlin
Expected identity: Gradle 9.7.1 through ./gradlew and a
supported Gradle runtime JVM (JDK 21 in this course). Do not replace
the Wrapper with a system gradle command.
3. Define plugin resolution before project structure
cat > settings.gradle.kts <<'EOF'
pluginManagement {
includeBuild("build-logic")
repositories {
gradlePluginPortal()
}
plugins {
id("com.diffplug.spotless") version "8.10.0"
}
}
rootProject.name = "gradle-plugin-lab"
include("app", "library")
EOF
includeBuild("build-logic") makes locally compiled
convention plugins available to the plugins DSL. The Spotless entry
supplies a default exact version; it still does not apply Spotless.
gradlePluginPortal() is the explicit external plugin
source.
4. Create the included convention-plugin build
cat > build-logic/settings.gradle.kts <<'EOF'
rootProject.name = "build-logic"
EOF
cat > build-logic/build.gradle.kts <<'EOF'
plugins {
`kotlin-dsl`
}
repositories {
mavenCentral()
}
EOF
cat > build-logic/src/main/kotlin/academy.java-conventions.gradle.kts <<'EOF'
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.api.tasks.testing.Test
import org.gradle.jvm.toolchain.JavaLanguageVersion
plugins {
java
}
java {
toolchain.languageVersion.set(JavaLanguageVersion.of(17))
}
tasks.withType<JavaCompile>().configureEach {
options.encoding = "UTF-8"
options.release.set(17)
}
tasks.withType<Test>().configureEach {
useJUnitPlatform()
}
tasks.register("conventionReport") {
group = "academy"
description = "Prints the repository-owned Java convention identity."
doLast {
println("plugin=academy.java-conventions")
println("javaRelease=17")
println("encoding=UTF-8")
}
}
EOF
The kotlin-dsl plugin is tied to the Gradle
distribution, while the build-logic build still needs a repository
such as Maven Central for Kotlin compiler dependencies used to
compile the precompiled script plugin. That
repositories block belongs to the separate build-logic
build; it is not the main build's plugin repository policy. The
convention plugin applies the core Java plugin itself before
configuring Java-specific model elements, and its task is registered
lazily.
5. Resolve the community plugin once at the root without applying it there
cat > build.gradle.kts <<'EOF'
plugins {
id("com.diffplug.spotless") apply false
}
EOF
The version is intentionally omitted here because
pluginManagement.plugins already supplies 8.10.0.
apply false resolves the plugin for the build while
keeping the root project free of Spotless tasks/extensions.
6. Apply convention/core/community plugins at deliberate project scope
cat > app/build.gradle.kts <<'EOF'
plugins {
id("academy.java-conventions")
application
id("com.diffplug.spotless")
}
application {
mainClass.set("dev.academy.app.Main")
}
spotless {
format("misc") {
target("*.md")
trimTrailingWhitespace()
endWithNewline()
}
}
EOF
cat > library/build.gradle.kts <<'EOF'
plugins {
id("academy.java-conventions")
`java-library`
}
EOF
cat > app/src/main/java/dev/academy/app/Main.java <<'EOF'
package dev.academy.app;
public final class Main {
public static void main(String[] args) {
System.out.println("plugin lab");
}
}
EOF
cat > library/src/main/java/dev/academy/lib/Message.java <<'EOF'
package dev.academy.lib;
public final class Message {
private Message() {}
public static String value() { return "library"; }
}
EOF
printf '%s
' '# Plugin Lab' '' 'Spotless checks this file.' > app/README.md
The convention plugin applies to both modules.
application and java-library remain
module-specific core plugins. Spotless is intentionally scoped only
to app.
7. Inspect tasks before running build work
./gradlew projects
./gradlew :app:tasks --all | grep -E 'spotless|conventionReport|compileJava|run' || true
./gradlew :library:tasks --all | grep -E 'spotless|conventionReport|compileJava' || true
./gradlew -p build-logic tasks --all | head -80
Expected evidence: both subprojects expose
conventionReport and Java compilation tasks; only
app exposes Spotless formatting/check tasks and the
application run task. This before/after model evidence
proves application scope.
8. Capture source/version policy separately from task output
Record the declared policy in a small evidence directory. This does not replace resolution logs; it proves what the repository asked for.
mkdir -p evidence
grep -n -E 'pluginManagement|gradlePluginPortal|com.diffplug.spotless|8.10.0|includeBuild' settings.gradle.kts build.gradle.kts app/build.gradle.kts > evidence/plugin-policy.txt
./gradlew :app:spotlessCheck --info --console=plain > evidence/spotless-info.log 2>&1
grep -E 'com.diffplug.spotless|spotless' evidence/spotless-info.log | head -40 || true
Logs vary with cache state and Gradle internals, so do not parse one
exact line as a security contract. Keep the settings/build-file
declaration and Wrapper identity as primary review evidence, and use
--info only as supporting resolution/execution
evidence.
9. Run convention evidence, formatting gate, and build
./gradlew :app:conventionReport :library:conventionReport --console=plain
./gradlew :app:spotlessCheck --console=plain
./gradlew build --console=plain | tee evidence/build.log
./gradlew :app:run --console=plain
Expected state: Java sources compile using release 17 conventions,
the formatting check passes for the small README target, and the
application prints plugin lab. No task mutates source
because spotlessCheck is a check;
spotlessApply would intentionally rewrite matching
files and should be used only when that mutation is desired.
10. Extensions are plugin-provided configuration surfaces
The application {} block exists because the Application
core plugin contributes that extension. The
spotless {} block exists because the community plugin
contributes its extension. The java {} block inside the
convention plugin exists because that plugin applies the Java core
plugin. If an accessor is missing, first ask whether the
corresponding plugin is actually applied at the point where the
build script expects it.
11. Controlled before/after version change is a build-logic migration
Do not change the Spotless version merely because the Portal has a newer number. A real upgrade should review release notes/source provenance, change the single version policy in settings, run a clean isolated test lane, compare task behavior/output, and preserve rollback. The lab deliberately stays on 8.10.0 throughout.
12. Challenge: where should formatting policy live if every JVM module needs it?
You now have two choices. Copy the spotless {} block
into every module, or create a dedicated repository-owned formatting
convention plugin that applies/configures Spotless. Choose the
latter when the policy is genuinely shared. To do it correctly, the
build-logic build must itself have the external plugin
implementation available on its build-logic classpath at an exact
reviewed version; do not assume the main build’s project plugin
classpath automatically becomes the convention-plugin build’s
compilation classpath.
13. Cleanup only disposable state
./gradlew --stop || true
cd ..
rm -rf gradle-plugin-lab
This removes only the lab and its isolated User Home. Never “fix”
plugin resolution by deleting the normal user
~/.gradle directory first.
Knowledge check
Why can id("com.diffplug.spotless") omit a version
in app/build.gradle.kts?
Because settings pluginManagement supplies the exact default version 8.10.0 for that plugin request.
Why does the root use apply false?
It resolves/declares the external plugin for the build without applying its model/tasks to the root project.
Why is academy.java-conventions versionless in
project scripts?
It is supplied from the included repository-owned build-logic source, not fetched as a versioned external plugin in this lab.
What proves Spotless is scoped only to app?
The app task listing includes Spotless tasks while the library task listing does not, and only app applies the plugin.
Why is spotlessCheck safer evidence than
spotlessApply for a read-only gate?
The check validates formatting without intentionally rewriting source files.
What must change if a convention plugin itself applies a community plugin?
The convention-plugin build must declare/pin the external plugin implementation on its own build-logic classpath or otherwise make it resolvable there.
14. Bridge to design choices
The lab demonstrated source, version, scope, and resulting model state. Lesson 3 turns those mechanics into architecture decisions: when to centralize policy, where build logic should live, how much autonomy subprojects should retain, and how to evaluate third-party plugin convenience against upgrade and supply-chain cost.
Official references and version notes
- Gradle 9.7.1 Release Notes — pinned Gradle baseline for this chapter.
- Introduction to Plugins and Working with Plugins — core/community/local plugin sources, plugins DSL, plugin management, and resolution.
- Convention Plugins and Precompiled Script Plugins.
-
Best Practices for Structuring Builds
— convention plugins and the current preference for an included
build-logicbuild over repeated cross-project configuration. - Composite Builds — included plugin builds and plugin resolution.
- PluginManagementSpec — plugin repositories, default plugin versions, resolution strategy, and included plugin builds.
- Task Configuration Avoidance — relevant when convention/plugin code contributes tasks.
- Gradle Plugin Portal: com.diffplug.spotless — controlled community-plugin example; version 8.10.0 was published August 17, 2026 and is configuration-cache compatible according to the Portal.
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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.