Chapter 18Lesson 02~225 minutes

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.

Core pluginsSpotless 8.10.0build-logicIncluded buildEvidence

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-logic build.
  • 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?

Why does the root use apply false?

Why is academy.java-conventions versionless in project scripts?

What proves Spotless is scoped only to app?

Why is spotlessCheck safer evidence than spotlessApply for a read-only gate?

What must change if a convention plugin itself applies a community plugin?

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

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.