ADR-00018: Build-enforced Java formatting via Spotless + an Eclipse JDT profile
Approval Status
Section titled “Approval Status”Status flow: Proposed → Accepted → Reviewed → Approved (or Rejected), or Not Required. A superseded ADR keeps its file; only its status changes to Superseded by NNNNN.
| Governance body | Status | Last update |
|---|---|---|
| CTN Project Team | Proposed | 2026-06-24 |
| CTN Technical Advisory Board | Pending | — |
| CTN Steering Committee | Pending | — |
| BDI Conformance Team | Pending | — |
| BDI Framework Team | Pending | — |
Context and Problem Statement
Section titled “Context and Problem Statement”asr-service (Quarkus) has no machine-enforced Java formatting. Sources today follow IntelliJ IDEA’s default formatting by convention only — there is no shared config, no apply command, and no CI gate. Contributors may use IntelliJ (primary), Eclipse, or VS Code, so formatting can drift per editor and per person, producing noisy diffs and review friction.
We want three properties:
- A single command formats the whole module (
mvn spotless:apply). - CI fails when any file is not formatted.
- All three IDEs auto-format identically to the build from one shared config.
Formatting is not linting. Spotless is a formatter: it rewrites layout — whitespace, indentation, line breaks, import order — to one canonical style. It does not judge what the code does: unused variables, likely bugs, null-safety, complexity, and security anti-patterns are all out of scope. Those are the job of a linter / static-analysis tool (Error Prone, SpotBugs, PMD, Checkstyle, SonarQube). A formatter makes code look the same; a linter checks that code is well-formed. This ADR is strictly about formatting; linting is a separate, not-yet-taken decision — see Follow-up: linting below.
The repository is a monorepo (CNCL-BDI) holding several components with deliberately separate builds: asr-service (nl.bdi.asr), visibility-dashboard/backend (nl.bdi.cloudtower), plus static/TS frontends and Keycloak assets. There is no parent reactor POM, and CI is path-scoped per subtree (see TDR-0004), whose first asr-service workflow reserves a format-gate placeholder. Any formatting decision must fit that monorepo shape rather than assume a single application.
Considered Options
Section titled “Considered Options”The core board-relevant choice is the formatting engine and the style source of truth, because it determines IDE parity, CI feasibility, and the size of the one-time reformat.
Option 1: spotless-maven-plugin driving an Eclipse JDT formatter profile (chosen)
Section titled “Option 1: spotless-maven-plugin driving an Eclipse JDT formatter profile (chosen)”- Description: Use
com.diffplug.spotless:spotless-maven-pluginwith its<eclipse>step pointed at a committedeclipse-formatter.xml. Eclipse imports the XML natively; IntelliJ achieves true parity via the Adapter for Eclipse Code Formatter plugin (delegates to the real Eclipse JDT engine); VS Code reads it viajava.format.settings.url. - Pros: One config consumed by all three IDEs; the Eclipse JDT formatter runs in-process and headless, so it is fast in CI;
spotless:checkbinds toverifyfor a zero-wiring local gate. - Cons: The Eclipse JDT engine differs from IntelliJ’s native engine, so native IntelliJ XML import is only approximate — true parity requires the per-contributor Adapter plugin.
Option 2: googleJavaFormat / palantirJavaFormat
Section titled “Option 2: googleJavaFormat / palantirJavaFormat”- Description: Adopt an opinionated, fixed formatter as the Spotless step.
- Pros: Zero style config to maintain; deterministic.
- Cons: Opinionated styles (e.g. 2-space indent) diverge heavily from the current 4-space, IntelliJ-default code — maximal one-time churn — and still need a per-IDE plugin for live parity. Not consumable as a shared Eclipse/IntelliJ/VS Code config.
Option 3: Spotless <idea> step
Section titled “Option 3: Spotless <idea> step”- Description: Let Spotless invoke IntelliJ’s own formatter.
- Cons: Requires a local IntelliJ binary and launches a separate IDEA process per file — unusable in CI and for non-IntelliJ contributors. Rejected outright.
Decision Outcome
Section titled “Decision Outcome”Chosen option: Option 1 — spotless-maven-plugin with an Eclipse JDT profile — together with the following scoping and operational decisions that make it fit the monorepo:
- Scope:
asr-serviceonly. Notvisibility-dashboard/backend, not the TS frontends. This fills TDR-0004’sformat-gate placeholder and avoids inventing shared formatting plumbing across builds that are deliberately separate — the same “no premature abstraction across notional consumers” reasoning TDR-0004 applied to CI. vis-dashboard can adopt the proven config later as its own installment. - Config location:
asr-service/config/eclipse-formatter.xml, referenced via${project.basedir}— self-contained inside the one subtree its path-scoped CI watches; no reactor /${maven.multiModuleProjectDirectory}path tricks. - Profile philosophy: clean canonical, not IntelliJ-mimicry. The profile is Eclipse built-in defaults with one deliberate override —
tabulation.char=space— at width 120, K&R braces, indent 4 (all of which already match the code). We explicitly do not run the laborious key-by-key tuning loop to approximate IntelliJ’s engine; once we accept a one-time reformat, fighting an engine difference the Adapter plugin already resolves buys nothing. The XML is kept complete (allorg.eclipse.jdt.core.formatter.*keys) so missing keys do not silently fall back to Eclipse’s tab-based defaults. - Import order is normalised, not preserved:
java, javax, jakarta, org, com, <others>thenstaticlast (<order>java,javax,jakarta,org,com,\#</order>). Reshuffle churn is accepted in exchange for an explicit, documented order. Wildcard imports are forbidden (<forbidWildcardImports/>; onejakarta.persistence.*is expanded), and IntelliJ’s*-collapse thresholds are raised to 999 to stop re-collapse drift. - IDE parity via the Adapter for Eclipse Code Formatter plugin (IntelliJ), and the Spotless eclipse formatter version is pinned to align with the plugin’s bundled engine. CI
spotless:checkis the authoritative gate; the plugin is a convenience so contributors rarely hit it. - Deferred reformat to protect in-flight branches. The 177-file reformat would force a massive rebase on every open branch, so it is split off in time (see Implementation Plan).
Rationale
Section titled “Rationale”Option 1 is the only one that satisfies property 3 (one config, all three IDEs) while remaining CI-viable (property 2) and low-churn against the current 4-space IntelliJ-default code. The two honest costs — the per-contributor Adapter plugin and an irreducible engine difference under native import — are mitigated by making the Adapter plugin the documented path and treating CI as the source of truth: a contributor without the plugin simply gets a gate failure pointing at mvn spotless:apply, never silently-divergent code. Scoping to asr-service and keeping the config inside that subtree mirrors the established monorepo house pattern and TDR-0004’s path-scoped CI.
Consequences
Section titled “Consequences”- Positive: One shared config across IDEs; fast headless CI gate; explicit import order and no wildcards; the config is self-contained in the
asr-servicesubtree; the build stays green at every commit because the gate is armed only when the tree becomes compliant. - Negative: True IntelliJ parity depends on a per-contributor plugin install and a version pin that must be kept aligned; a one-time 177-file reformat commit;
visibility-dashboardis left ungated for now (a future installment). Repo-root IntelliJ code style appliesasr-stylemonorepo-wide (IntelliJ code style cannot be scoped to a subtree), so editing vis-dashboard Java in IntelliJ will nudge it towardasr-styleon save — accepted as harmless eventual consistency, not a contract. - Neutral: vis-dashboard adoption, a shared pre-push hook, and TDR-0004’s two-layer CI split each remain separate future decisions.
Implementation Plan
Section titled “Implementation Plan”- Plumbing commit (now), on a branch off clean
main, build stays green:asr-service/pom.xml: Spotless plugin<configuration>only — java includes,<eclipse>step with pinned version +${project.basedir}/config/eclipse-formatter.xml, the<importOrder>,removeUnusedImports,forbidWildcardImports,trimTrailingWhitespace,endWithNewline. No<execution>block —spotless:apply/checkare runnable on demand but nothing binds toverify, so the gate does not fail on still-unformatted code.asr-service/config/eclipse-formatter.xml(the canonical profile,name="asr-style").- Repo-root IDE files:
.editorconfig(universal settings +[*.java]space/4),.vscode/settings.json(java.format.settings.url→asr-service/config/eclipse-formatter.xml, profileasr-style),.idea/codeStyles/Project.xml+codeStyleConfig.xml(un-ignore exactly these two in.gitignore). asr-service/CONTRIBUTING.md: the two commands, config location, Adapter plugin + pinned version, wildcard-threshold note.
- Deferred reformat commit (later): once in-flight branches have merged, rebase this branch onto updated
main, runmvn spotless:apply, commit the reformat together with adding the<execution>check→verifybinding (gate armed exactly when the tree is compliant), then add.git-blame-ignore-revscarrying that commit’s SHA sogit blameskips the mass reformat. Merge. - CI wiring is owned by TDR-0004: this ADR delivers pom + config + baseline only; TDR-0004’s
formatjob callsmvn spotless:check, path-scoped toasr-service/**.
Compliance and Validation
Section titled “Compliance and Validation”-
mvn spotless:applyis idempotent (re-run produces no changes). - After the reformat commit,
mvn spotless:checkpasses on the committed tree. - A deliberate mis-format makes
mvn verifyfail with a Spotless violation pointing toapply. - IntelliJ “Reformat Code” (Adapter plugin, pinned version) round-trips without re-dirtying files.
- Wildcard imports do not reappear after IntelliJ optimize-imports.
- VS Code format-on-save round-trips cleanly against the build.
Follow-up: linting (separate, not-yet-taken decision)
Section titled “Follow-up: linting (separate, not-yet-taken decision)”Formatting (this ADR) and linting are complementary and both are cheap to enforce in the same path-scoped CI, so linting is a natural next installment. It is not decided here; the recommendation below is a starting point for that decision.
Recommended direction, mirroring this ADR’s philosophy (build-enforced, asr-service-scoped, low-churn, introduced non-gating then armed once the baseline is clean, CI job owned by TDR-0004):
- Error Prone as the primary linter. A javac plugin that catches real bugs at compile time (null dereferences, format-string mistakes, broken
equals/hashCode, mutable-static leaks) with famously low false positives and near-zero runtime cost. Optionally pair it with NullAway for null-safety. Best value for the effort — gate on a curated ruleset. - SpotBugs + FindSecBugs as a security pass. Bytecode analysis for bug and security patterns. Because the ASR handles authentication, tokens, and PII, a security-focused linter earns its place. Start report-only, then gate on high-severity findings.
- Skip Checkstyle for formatting. Spotless already owns layout, so Checkstyle would overlap and add noise. Only reconsider it later for non-format conventions (naming, Javadoc presence) if a concrete need appears.
- Treat SonarQube / SonarCloud as a later “quality dashboard” decision. It aggregates linters and adds a quality gate plus security hotspots, but brings infrastructure. Worthwhile once there is appetite for a dashboard — not the first step.
Rollout should follow the same pattern as the Spotless gate: introduce the tool non-gating, fix the baseline in a dedicated commit (with .git-blame-ignore-revs if it touches many files), then arm the CI gate exactly when the tree is clean. Config stays inside the asr-service subtree; the CI job is owned by TDR-0004. When taken, this warrants its own ADR.
Related Documents
Section titled “Related Documents”- TDR-0004 — asr-service CI is a single path-scoped workflow; owns the
formatjob that runs this gate. - ADR-00002 — preferred technology stack (Spotless is a candidate addition to its build-tooling row).
- ADR-00009 — layered source structure / monorepo shape.
- Handoff:
.scratch/spotless-formatting-handoff.md(original plan; this ADR amends its single-module and minimal-churn-mimicry assumptions).
Status History
Section titled “Status History”| Date | Status | Notes |
|---|---|---|
| 2026-06-24 | Proposed | Initial proposal |
ADR format based on Michael Nygard’s template with CTN-specific enhancements
In samenwerking met




