T-003 — Convention plugins, Spotless and Google Java Style
Picking this up? Read
CONTRIBUTING.mdfirst, then claim the matching issue and work on a branch. Finished means all six definition-of-done gates, not five. If anything below disagrees with a contract, the contract wins — open a contract change issue instead of implementing either version. Update this task’s row in the ledger in the same pull request.
Milestone: M0 Foundations · Estimate: 30 min (at the limit; if the -Werror clean-up of the
generated stubs overruns, stop after the java convention plugin builds and hand the spring convention
plugin to a follow-up session, noting it in tasks/README.md)
Preconditions — T-001 (ten Gradle projects, empty source trees, Gradle 9.5 wrapper) and
T-002 (gradle/libs.versions.toml complete, not yet consumed). No module has a build.gradle.kts;
nothing applies a plugin.
Goal — Make build-logic the single place where compilation, formatting, Lombok policy and test
setup are configured, and apply it to all nine modules so every module is configured identically.
1. Why this task exists
Nine modules configured by one plugin instead of nine drifting copies of the same block
(#ct5-modules). More sharply: this project’s whole claim is that two lock backends are observably
identical, and that argument is weaker if the two store implementations compile under different
warning settings. -Werror plus google-java-format also removes review bandwidth spent on style so it
can be spent on lease arithmetic.
2. Contracts to obey
| What | Pinned by |
|---|---|
Java 25 toolchain, Spotless + google-java-format, Lombok limited to @RequiredArgsConstructor/@Slf4j, test conventions in build-logic |
docs/contracts/C5-config-build-and-naming.md#ct5-modules (build-logic row) |
| Plugin/library aliases and the no-version-literals rule | docs/contracts/C5-config-build-and-naming.md#ct5-catalog |
| Which modules are Spring Boot applications | #ct5-catalog (spring-boot-plugin Used-by) + #ct5-modules |
lock-api gets no Lombok and no third-party dependency |
docs/contracts/C2-java-api.md#ct2-zero-dep |
| Single Gradle build, Kotlin DSL | docs/adr/ADR-010-monorepo-single-gradle-build.md |
Precedence: if this spec and a contract disagree, the contract wins — stop and report both
(docs/04-contracts.md#c-precedence).
3. Deliverables
| Path | What |
|---|---|
build-logic/settings.gradle.kts, build-logic/build.gradle.kts |
Included build; applies kotlin-dsl; resolves the root catalog |
build-logic/src/main/kotlin/dlock.java-conventions.gradle.kts |
Toolchain, compiler args, Spotless, Lombok policy, test conventions |
build-logic/src/main/kotlin/dlock.spring-conventions.gradle.kts |
Applies java-conventions + Spring Boot plugin + BOM + actuator/micrometer |
build-logic/src/main/kotlin/dlock.library-conventions.gradle.kts |
For non-application Java modules (lock-api, lock-client) — no Boot plugin, no fat jar |
config/spotless/license-header.txt |
The license/attribution header applied to every .java file |
lock-api/build.gradle.kts … harness/build.gradle.kts (8 files) |
One line each: which convention plugin applies. deploy gets none |
README.md (repo root) |
Already exists — do not replace it. Update only the Getting started block so the build commands it shows are the ones this task makes real |
4. Specification
java-conventions must set: a Java toolchain of 25 (language version, not just source/target, so
the build is reproducible off a different local JDK); compiler args -Xlint:all and -Werror;
UTF-8 encoding; -parameters. Test conventions: JUnit Platform, the JUnit BOM plus the assertj alias
from the catalog, maxParallelForks conservative (lease-timing tests are wall-clock sensitive — one or
two forks, never availableProcessors()), and test logging that shows failed and skipped tests.
Lombok is added as compileOnly + annotationProcessor (both main and test) by
java-conventions, with an explicit exclusion for lock-api — implement the exclusion by not
applying java-conventions’ Lombok block when the project name is lock-api, or by giving lock-api a
convention plugin that omits it. Prefer the second: a positive rule beats a negative special case. The
policy that only @RequiredArgsConstructor and @Slf4j may be used is enforced in review and by a
Spotless custom rule or a grep-based check task that fails on any other lombok. import.
Spotless configuration: java { googleJavaFormat(<catalog version>) }, licenseHeaderFile,
removeUnusedImports, trimTrailingWhitespace, endWithNewline; also format *.gradle.kts with
ktlint or at minimum trailing-whitespace/newline steps. Wire check to depend on spotlessCheck.
The header file names the project, the fictional operator “Northwind Pay” (a fictional mid-size payment
service provider), and an Apache-2.0 line. No real company, partner or product name anywhere.
spring-conventions applies java-conventions, the spring-boot-plugin alias, imports the
spring-boot-bom platform, and adds the actuator + micrometer-prometheus bundle from T-002 to every
service. It applies to the five services listed in the spring-boot-plugin Used-by column plus
harness only if harness needs a Spring context — if the contract does not say, leave harness on
java-conventions and note the open question.
Each module build.gradle.kts applies exactly one convention plugin and, at this stage, no
dependencies on other modules — inter-module wiring lands with the tasks that need it. Put nothing in
these files that the convention plugin could own.
5. Acceptance criteria
./gradlew buildisBUILD SUCCESSFUL;./gradlew spotlessCheckpasses on an empty source set../gradlew javaToolchainsand./gradlew -q :lock-api:compileJava --info | grep -i releaseshow Java 25 in use for at least one module../gradlew :lock-api:dependencies --configuration compileClasspathshows no external module — Lombok included.-Werroris effective: adding a deliberate raw-type or unused-import in a scratch file makes./gradlew :lock-client:compileJavafail; remove the scratch file afterwards../gradlew spotlessApplyinserts the license header into a header-less.javafile, andspotlessCheckthen passes.- No version literal in any
.gradle.kts:grep -rnE '"[0-9]+\.[0-9]+' --include='*.gradle.kts' .is silent (catalog references only). - Every module except
deployhas abuild.gradle.ktswhoseplugins {}block names exactly onedlock.*-conventionsplugin.
6. Verification
./gradlew build spotlessCheck && ./gradlew :lock-api:dependencies --configuration compileClasspath
./gradlew javaToolchains
grep -rn 'lombok\.' --include='*.java' lock-api/ # expect: no output
Expected: BUILD SUCCESSFUL; lock-api compile classpath reports “No dependencies”; toolchain 25 listed.
7. Out of scope
Any production Java type — lock-api’s types are T-004. The GitHub Actions workflow that runs
check, and the CI dependency-count assertion for NFR-16, belong to T-007/T-008. Docker/Jib image
building (M5). Testcontainers wiring (first needed in M1). Do not add jetcd, Flyway or the
Postgres driver to any module here.
8. Hazards
- Do not install a pre-commit hook. Formatting is enforced by
spotlessCheckin CI, which is visible to reviewers; a local hook is invisible and silently diverges between contributors. -Werrorplus-Xlint:allwill fail on Lombok-generated code paths and onthis-escapein some Spring configurations. Suppress narrowly and with a comment (a single-Xlint:-this-escapeat most), never by dropping-Werror— a warning budget that starts at zero stays at zero.- Applying the Spring Boot plugin to
lock-apiorlock-clientproduces a bootJar and, worse, drags a framework onto the contract classpath — the exact failure#ct2-zero-depforbids. - google-java-format on Java 25 needs JVM
--add-exportsflags; if Spotless fails to launch, set them ingradle.propertiesfor the Spotless step only, and record it as a deviation.
9. On completion
Mark T-003 done in tasks/README.md. Record: the exact -Xlint suppressions you kept and why, whether
harness got spring- or java-conventions, and any --add-exports workaround.