Build CoreOrdered learning track

Code Quality Checks in the Maven Build

Learn Maven In Action - Part 022

Production-grade Maven code quality checks: Checkstyle, PMD, SpotBugs, formatters, license checks, quality gates, fail-fast strategy, reporting, suppressions, and governance.

11 min read2173 words
PrevNext
Lesson 2240 lesson track0922 Build Core
#maven#java#build-system#code-quality+5 more

Part 022 — Code Quality Checks in the Maven Build

Target: setelah bagian ini, kamu bisa membangun quality gate Maven yang tegas tapi tidak toxic: style konsisten, bug pattern tertangkap, license/header terjaga, static analysis terukur, suppression terkendali, dan CI memberi sinyal yang bisa dipercaya.

Code quality check di Maven sering dipasang seperti checklist:

<plugin>
  <artifactId>maven-checkstyle-plugin</artifactId>
</plugin>

Lalu build mulai gagal karena import order, whitespace, warning lama, false positive, atau plugin lambat.

Team frustrasi. Akhirnya semua orang menjalankan:

mvn verify -Dcheckstyle.skip -Dpmd.skip -Dspotbugs.skip

Itu bukan governance. Itu ritual yang gagal.

Quality checks yang baik harus menjawab:

  • aturan apa yang dijaga?
  • kenapa aturan itu penting?
  • kapan build harus gagal?
  • kapan cukup report-only?
  • siapa owner suppression?
  • apakah rule berlaku untuk semua module?
  • apakah generated code dikecualikan?
  • apakah CI bisa membedakan style violation, bug pattern, security issue, dan license issue?
  • apakah developer bisa memperbaiki masalah secara lokal tanpa trial-and-error?

Quality gate bukan soal membuat build galak. Quality gate adalah control surface untuk mengurangi defect, drift, dan review noise.


1. Mental Model: Quality Checks sebagai Build Control Plane

Ada beberapa jenis quality check. Jangan campur semuanya dalam satu kategori “lint”.

Check TypeExample ToolMain PurposeTypical Failure Mode
formattingSpotless, formatter pluginconsistent diffsnoisy diffs/review churn
styleCheckstylecoding standardreadability drift
bug patternSpotBugslikely defectnullness/concurrency/resource bug
code smellPMDsuspicious patternmaintainability risk
duplicate codeCPDduplication detectioncopy-paste divergence
license/headerlicense pluginlegal/compliancemissing/invalid header
dependency governanceEnforcer/OWASP/etc.supply-chain policybanned/vulnerable dependency
architectureArchUnit/custom testsboundary controlforbidden package/module dependency

This part focuses on code quality checks. Dependency governance gets deeper in Part 023 and Part 029.


2. Lifecycle Placement

Quality checks biasanya masuk verify, bukan compile.

Kenapa verify?

  • code sudah compile,
  • tests sudah jalan,
  • generated sources mungkin sudah dibuat,
  • artifact siap diverifikasi,
  • CI convention jelas: mvn verify = full local verification sebelum publish.

Tapi ada pengecualian:

CheckGood PhaseReason
format checkvalidate or verifybisa cepat fail sebelum compile
license headervalidate or verifytidak butuh compile
Checkstyleverify oftensource-level rule
PMDverifysource analysis, can be slower
SpotBugsverifyneeds compiled bytecode
ArchUnittestjust tests

Rule praktis:

Fast deterministic checks can fail early.
Slow or bytecode-based checks belong later.

3. Centralize Plugin Versions in Parent

Parent POM:

<properties>
  <maven.checkstyle.plugin.version>YOUR_APPROVED_VERSION</maven.checkstyle.plugin.version>
  <maven.pmd.plugin.version>YOUR_APPROVED_VERSION</maven.pmd.plugin.version>
  <spotbugs.maven.plugin.version>YOUR_APPROVED_VERSION</spotbugs.maven.plugin.version>
  <spotless.maven.plugin.version>YOUR_APPROVED_VERSION</spotless.maven.plugin.version>
</properties>

<build>
  <pluginManagement>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-checkstyle-plugin</artifactId>
        <version>${maven.checkstyle.plugin.version}</version>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-pmd-plugin</artifactId>
        <version>${maven.pmd.plugin.version}</version>
      </plugin>
      <plugin>
        <groupId>com.github.spotbugs</groupId>
        <artifactId>spotbugs-maven-plugin</artifactId>
        <version>${spotbugs.maven.plugin.version}</version>
      </plugin>
      <plugin>
        <groupId>com.diffplug.spotless</groupId>
        <artifactId>spotless-maven-plugin</artifactId>
        <version>${spotless.maven.plugin.version}</version>
      </plugin>
    </plugins>
  </pluginManagement>
</build>

Kenapa property version?

  • upgrade terpusat,
  • mudah override sementara di branch/platform,
  • dependency bot bisa update controlled,
  • audit lebih mudah,
  • CI matrix bisa test upgrade plugin.

Jangan declare version tersebar di tiap module.


4. Separate Policy from Execution

Struktur yang sehat:

build-tools/
├── checkstyle/
│   ├── checkstyle.xml
│   └── suppressions.xml
├── pmd/
│   ├── ruleset.xml
│   └── suppressions.xml
├── spotbugs/
│   └── exclude.xml
└── license/
    └── header.txt

Atau sebagai artifact internal:

com.acme.build:acme-quality-rules:1.12.0

Ada dua model.

Model A: rules checked into repo

Kelebihan:

  • easy to inspect,
  • versioned with application,
  • local change easy.

Kekurangan:

  • drift antar repo,
  • copy-paste rules,
  • update sulit di banyak repo.

Model B: rules packaged as internal artifact

Kelebihan:

  • central governance,
  • consistent rules,
  • easier fleet upgrade.

Kekurangan:

  • lebih kompleks,
  • developer perlu tahu cara debug resolved rule,
  • breaking change bisa berdampak luas.

Enterprise biasanya memakai hybrid:

baseline rules as internal artifact
repo-specific suppressions checked into repo

5. Checkstyle: Style as Review Noise Reduction

Checkstyle cocok untuk:

  • import order,
  • naming convention,
  • braces/whitespace policy,
  • max line length,
  • class/member ordering,
  • Javadoc policy jika team memang butuh,
  • avoiding review debate.

Contoh plugin:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-checkstyle-plugin</artifactId>
  <configuration>
    <configLocation>build-tools/checkstyle/checkstyle.xml</configLocation>
    <suppressionsLocation>build-tools/checkstyle/suppressions.xml</suppressionsLocation>
    <includeTestSourceDirectory>true</includeTestSourceDirectory>
    <consoleOutput>true</consoleOutput>
    <failsOnError>true</failsOnError>
    <linkXRef>false</linkXRef>
  </configuration>
  <executions>
    <execution>
      <id>checkstyle-verify</id>
      <phase>verify</phase>
      <goals>
        <goal>check</goal>
      </goals>
    </execution>
  </executions>
</plugin>

Important distinction:

GoalUse
checkstyle:checkstylegenerate report
checkstyle:checkfail build on violation

Quality gate di CI biasanya pakai check, bukan hanya report.

Checkstyle rule design

Good rules:

- objective
- automatically fixable or easy to fix
- aligned with team style
- low false positive
- stable across IDEs

Bad rules:

- subjective naming preferences with no operational value
- excessive Javadoc requirements for all internals
- line length rule that fights generated SQL/regex/test data
- rules that create more suppression than signal

Style rules harus mengurangi review noise, bukan mengganti engineering judgment.


6. Formatter vs Checkstyle

Ada dua pendekatan:

Formatter-first

Gunakan tool seperti Spotless untuk auto-format.

mvn spotless:apply
mvn spotless:check

Pros:

  • developer tidak manual memperbaiki whitespace/import,
  • fewer review comments,
  • deterministic format.

Cons:

  • format changes besar saat adoption,
  • perlu IDE alignment,
  • generated/source exclusions penting.

Checkstyle-first

Checkstyle hanya melaporkan violation.

Pros:

  • granular style policy,
  • lebih luas dari formatting.

Cons:

  • bisa noisy,
  • beberapa issue manual fix.

Pattern senior:

formatter handles mechanical formatting
Checkstyle handles semantic style rules not covered by formatter

Jangan membuat Checkstyle bertarung dengan formatter.


7. Spotless Baseline

Contoh:

<plugin>
  <groupId>com.diffplug.spotless</groupId>
  <artifactId>spotless-maven-plugin</artifactId>
  <configuration>
    <java>
      <googleJavaFormat />
      <removeUnusedImports />
      <formatAnnotations />
    </java>
  </configuration>
  <executions>
    <execution>
      <id>format-check</id>
      <phase>validate</phase>
      <goals>
        <goal>check</goal>
      </goals>
    </execution>
  </executions>
</plugin>

Local developer:

mvn spotless:apply

CI:

mvn verify

Adoption tip:

Do one mechanical formatting commit before feature work.
Do not mix format adoption with business logic changes.

8. PMD: Source-Level Code Smells

PMD analyzes source code and detects common problematic patterns.

Typical categories:

  • unused code,
  • overly complex code,
  • suspicious conditionals,
  • empty catch blocks,
  • poor exception handling,
  • duplicate code via CPD,
  • avoidable object creation,
  • questionable design patterns.

Example:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-pmd-plugin</artifactId>
  <configuration>
    <rulesets>
      <ruleset>build-tools/pmd/ruleset.xml</ruleset>
    </rulesets>
    <printFailingErrors>true</printFailingErrors>
    <linkXRef>false</linkXRef>
    <includeTests>true</includeTests>
    <targetJdk>${maven.compiler.release}</targetJdk>
  </configuration>
  <executions>
    <execution>
      <id>pmd-check</id>
      <phase>verify</phase>
      <goals>
        <goal>check</goal>
        <goal>cpd-check</goal>
      </goals>
    </execution>
  </executions>
</plugin>

PMD adoption strategy

PMD can be noisy. Start with curated rules.

Good first rules:

- empty catch/finally
- unnecessary fully qualified name
- unused private field/method
- avoid duplicate literals where useful
- suspicious equals/hashCode
- broken switch/fallthrough-like cases

Avoid early:

- aggressive design metrics
- low line-count thresholds
- broad complexity rules with no team agreement
- rules that conflict with framework patterns

PMD should catch defects and maintainability hazards, not enforce taste blindly.


9. CPD: Duplicate Code Detection

Copy-paste detection can be valuable, but dangerous if misused.

Useful for:

  • duplicated business rules,
  • duplicated SQL builders,
  • duplicated mapping logic,
  • duplicated workflow guard logic.

Noisy for:

  • generated code,
  • DTOs,
  • test fixtures,
  • repetitive assertions,
  • enum mapping tables,
  • configuration classes.

Config strategy:

<configuration>
  <minimumTokens>100</minimumTokens>
  <excludeRoots>
    <excludeRoot>target/generated-sources</excludeRoot>
  </excludeRoots>
</configuration>

Governance rule:

CPD should detect meaningful duplication, not punish unavoidable structural similarity.

10. SpotBugs: Bytecode-Level Bug Patterns

SpotBugs analyzes compiled bytecode, so it usually belongs after compile and often in verify.

It can catch:

  • null dereference patterns,
  • ignored return values,
  • concurrency hazards,
  • resource leaks,
  • equality/hashCode mistakes,
  • serialization problems,
  • bad practice patterns.

Example:

<plugin>
  <groupId>com.github.spotbugs</groupId>
  <artifactId>spotbugs-maven-plugin</artifactId>
  <configuration>
    <effort>Max</effort>
    <threshold>Medium</threshold>
    <xmlOutput>true</xmlOutput>
    <excludeFilterFile>build-tools/spotbugs/exclude.xml</excludeFilterFile>
  </configuration>
  <executions>
    <execution>
      <id>spotbugs-check</id>
      <phase>verify</phase>
      <goals>
        <goal>check</goal>
      </goals>
    </execution>
  </executions>
</plugin>

Effort and threshold

SettingMeaning
efforthow much analysis work SpotBugs performs
thresholdminimum confidence/severity class reported

Adoption pattern:

start Medium threshold
fix high-confidence bugs
then expand rule coverage

Do not start with maximum strictness across a legacy codebase unless you have time to remediate.


11. Generated Code Boundary

Generated code should rarely be subject to the same checks as handwritten code.

Examples:

target/generated-sources/openapi
target/generated-sources/annotations
target/generated-sources/protobuf

Rule:

You govern generated code by governing generator input and generator version.
You do not manually style-police generated output.

Exclude generated sources from style checks unless there is a specific reason.

Bad:

Checkstyle fails because generated OpenAPI model has long line.

Better:

Check OpenAPI spec quality separately.
Exclude generated Java output from formatting/style gates.

12. Test Code Rules

Should quality checks apply to tests?

Yes, but not always with identical rules.

Test code often needs:

  • expressive names,
  • longer method names,
  • repeated literals,
  • fixture setup,
  • assertions with readable structure.

Separate test rules can help:

main source: strict production rules
test source: readability-focused rules

Example policy:

Rule CategoryMain CodeTest Code
unused importsfailfail
formattingfailfail
line lengthstrictrelaxed
magic numbersstrictrelaxed
duplicated literalsstrictrelaxed
empty catchfailfail
Javadocmaybeno

13. Suppression Policy

Suppressions are necessary. Uncontrolled suppressions are technical debt hiding as configuration.

Bad suppression:

<suppress checks=".*" files=".*" />

Better suppression:

<suppress
  checks="LineLength"
  files=".*Generated.*" />

Best suppression includes reason and scope:

<!-- Generated by OpenAPI Generator. Style governed by spec and generator version. -->
<suppress
  checks="LineLength|Javadoc.*"
  files=".*/target/generated-sources/openapi/.*" />

For SpotBugs:

<Match>
  <Class name="com.acme.generated.*" />
  <Bug pattern="EI_EXPOSE_REP" />
</Match>

Governance rule:

Every broad suppression must explain why and where it expires or how it is bounded.

14. Baseline Existing Violations

Legacy codebase problem:

You add Checkstyle/PMD/SpotBugs and get 8,000 violations.

Options:

Option A: Big Bang Fix

Pros:

  • clean baseline,
  • simple policy,
  • strong signal.

Cons:

  • huge diff,
  • merge conflicts,
  • expensive,
  • business work blocked.

Option B: Report-only First

Pros:

  • low disruption,
  • learn violation shape.

Cons:

  • no enforcement,
  • can become permanent.

Option C: New-code Gate

Pros:

  • prevents worsening,
  • gradual cleanup.

Cons:

  • needs tooling/pipeline support,
  • baseline management complexity.

Practical enterprise adoption:

1. Run report-only for one sprint.
2. Fix mechanical formatter issues in one dedicated commit.
3. Enable fail gate for low-noise rules.
4. Create bounded suppressions for known legacy hotspots.
5. Track remaining violations as explicit debt.
6. Increase strictness gradually.

15. Fail-Fast vs Report-Only

Not every rule should fail build immediately.

SignalMode
formatting driftfail
missing license headerfail
high-confidence SpotBugs bugfail
broad style preferencereport first
low-confidence smellreport first
duplicate code in legacy areareport first
generated code violationexclude or report separately

Quality gate maturity:

Level 0: no checks
Level 1: reports only
Level 2: fail for mechanical deterministic checks
Level 3: fail for high-confidence defect checks
Level 4: governed suppressions and fleet-wide reporting
Level 5: quality signals integrated with ownership and release policy

Aim for Level 3 quickly, then grow toward Level 5.


16. Maven Profile for Strictness

Sometimes you need different strictness locally vs CI.

Be careful. This can hide truth.

Acceptable:

<profiles>
  <profile>
    <id>ci</id>
    <activation>
      <property>
        <name>env.CI</name>
      </property>
    </activation>
    <properties>
      <quality.failOnViolation>true</quality.failOnViolation>
    </properties>
  </profile>
</profiles>

But better if mvn verify locally and CI are close.

Recommended:

local default = same checks as CI
developer convenience = explicit opt-out only for local fast loop
main/release CI = no opt-out

Example:

mvn verify -Dquality.skip=true

This should be allowed locally only, not in protected CI.


17. Skip Flags as Controlled Escape Hatch

Define skip flags explicitly:

<properties>
  <quality.skip>false</quality.skip>
  <checkstyle.skip>${quality.skip}</checkstyle.skip>
  <pmd.skip>${quality.skip}</pmd.skip>
  <spotbugs.skip>${quality.skip}</spotbugs.skip>
</properties>

Then enforce in CI with pipeline policy:

Protected branch build must not pass -Dquality.skip=true.
Release build must not pass quality skip flags.
Emergency bypass requires approval and issue link.

Escape hatches are useful. Silent bypasses are dangerous.


18. Multi-Module Quality Strategy

In multi-module project:

root
├── parent
├── bom
├── domain
├── api
├── persistence
├── app
└── generated-client

Not all checks apply equally.

ModuleCheckstylePMDSpotBugsNotes
parent POMnononouse Maven Enforcer instead
BOMnononodependency governance only
domainyesyesyeshigh-value
api DTOyesmaybemaybegenerated/DTO rules need tuning
persistenceyesyesyesresource/SQL patterns important
appyesyesyesintegration code quality
generated-clientlimitednomaybe nogovern generator instead

Do not force the same rule set blindly across generated, handwritten, parent, BOM, and deployable modules.


19. Aggregated Reporting

Each module emits reports under its own target.

module-a/target/checkstyle-result.xml
module-b/target/pmd.xml
module-c/target/spotbugsXml.xml

CI should publish these as artifacts or annotate PR.

Useful patterns:

**/target/checkstyle-result.xml
**/target/pmd.xml
**/target/cpd.xml
**/target/spotbugsXml.xml

If developers cannot find the violation quickly, the gate is too opaque.

Quality failure message should answer:

which module?
which file?
which rule?
why failed?
how to reproduce locally?
how to suppress if justified?

20. IDE Alignment

A Maven quality gate that fights the IDE creates friction.

Minimum alignment:

- same formatter config
- same import order
- same line endings
- same encoding UTF-8
- same generated-source exclusions
- same annotation processor setup

Commit supporting files:

.editorconfig
build-tools/checkstyle/checkstyle.xml
build-tools/pmd/ruleset.xml
build-tools/spotbugs/exclude.xml

.editorconfig example:

root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

[*.java]
indent_style = space
indent_size = 4

Maven checks are the source of truth, but IDE should make the right thing easy.


21. Performance Engineering for Quality Checks

Quality checks can slow build.

Common cost drivers:

- SpotBugs effort Max on huge module
- PMD rules over generated code
- CPD scanning test fixtures
- formatter checking all files every time
- multi-module duplicate plugin execution
- no CI cache for plugin dependencies

Optimization checklist:

[ ] Exclude generated code.
[ ] Avoid running Java source checks on non-Java modules.
[ ] Use module-specific execution only where needed.
[ ] Keep rulesets curated.
[ ] Tune SpotBugs threshold/effort.
[ ] Separate very expensive checks into nightly job if not release-critical.
[ ] Keep PR gate strict for high-signal checks.

Do not remove checks just because they are slow. First find why they are slow.


22. Quality Gates and Pull Request UX

A good PR quality gate:

  • fails quickly for mechanical issues,
  • gives file/line/rule,
  • suggests local command,
  • avoids dumping thousands of lines,
  • distinguishes new violation from old debt,
  • links to rule documentation or internal guide.

Bad:

Failed to execute goal org.apache.maven.plugins:maven-pmd-plugin:...:check

Better CI annotation:

module: pricing-domain
file: Money.java:42
rule: AvoidDecimalLiteralsInBigDecimalConstructor
reason: BigDecimal(double) can introduce precision error
local fix: mvn -pl pricing-domain pmd:check

Even if Maven plugin emits XML, your CI can surface it better.


23. Rule Ownership

Quality rules need owners.

Recommended ownership:

Rule AreaOwner
formattingplatform/build team
Checkstylelanguage guild/platform
PMDplatform + service owners
SpotBugsplatform + senior engineers
license/headerlegal/compliance/platform
generated code exclusionsowning generator team
suppressionsmodule owner

Without ownership, rules rot.

Rule lifecycle:


24. Example Enterprise Quality Parent

<project>
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.acme.platform</groupId>
  <artifactId>acme-service-parent</artifactId>
  <version>1.18.0</version>
  <packaging>pom</packaging>

  <properties>
    <quality.skip>false</quality.skip>
    <checkstyle.skip>${quality.skip}</checkstyle.skip>
    <pmd.skip>${quality.skip}</pmd.skip>
    <spotbugs.skip>${quality.skip}</spotbugs.skip>

    <maven.checkstyle.plugin.version>YOUR_APPROVED_VERSION</maven.checkstyle.plugin.version>
    <maven.pmd.plugin.version>YOUR_APPROVED_VERSION</maven.pmd.plugin.version>
    <spotbugs.maven.plugin.version>YOUR_APPROVED_VERSION</spotbugs.maven.plugin.version>
    <spotless.maven.plugin.version>YOUR_APPROVED_VERSION</spotless.maven.plugin.version>
  </properties>

  <build>
    <pluginManagement>
      <plugins>
        <plugin>
          <groupId>com.diffplug.spotless</groupId>
          <artifactId>spotless-maven-plugin</artifactId>
          <version>${spotless.maven.plugin.version}</version>
          <configuration>
            <java>
              <googleJavaFormat />
              <removeUnusedImports />
            </java>
          </configuration>
          <executions>
            <execution>
              <id>spotless-check</id>
              <phase>validate</phase>
              <goals>
                <goal>check</goal>
              </goals>
            </execution>
          </executions>
        </plugin>

        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-checkstyle-plugin</artifactId>
          <version>${maven.checkstyle.plugin.version}</version>
          <configuration>
            <configLocation>build-tools/checkstyle/checkstyle.xml</configLocation>
            <suppressionsLocation>build-tools/checkstyle/suppressions.xml</suppressionsLocation>
            <consoleOutput>true</consoleOutput>
            <failsOnError>true</failsOnError>
            <linkXRef>false</linkXRef>
          </configuration>
          <executions>
            <execution>
              <id>checkstyle</id>
              <phase>verify</phase>
              <goals>
                <goal>check</goal>
              </goals>
            </execution>
          </executions>
        </plugin>

        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-pmd-plugin</artifactId>
          <version>${maven.pmd.plugin.version}</version>
          <configuration>
            <rulesets>
              <ruleset>build-tools/pmd/ruleset.xml</ruleset>
            </rulesets>
            <printFailingErrors>true</printFailingErrors>
            <linkXRef>false</linkXRef>
          </configuration>
          <executions>
            <execution>
              <id>pmd</id>
              <phase>verify</phase>
              <goals>
                <goal>check</goal>
                <goal>cpd-check</goal>
              </goals>
            </execution>
          </executions>
        </plugin>

        <plugin>
          <groupId>com.github.spotbugs</groupId>
          <artifactId>spotbugs-maven-plugin</artifactId>
          <version>${spotbugs.maven.plugin.version}</version>
          <configuration>
            <effort>Max</effort>
            <threshold>Medium</threshold>
            <xmlOutput>true</xmlOutput>
            <excludeFilterFile>build-tools/spotbugs/exclude.xml</excludeFilterFile>
          </configuration>
          <executions>
            <execution>
              <id>spotbugs</id>
              <phase>verify</phase>
              <goals>
                <goal>check</goal>
              </goals>
            </execution>
          </executions>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

Module activation:

<build>
  <plugins>
    <plugin>
      <groupId>com.diffplug.spotless</groupId>
      <artifactId>spotless-maven-plugin</artifactId>
    </plugin>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-checkstyle-plugin</artifactId>
    </plugin>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-pmd-plugin</artifactId>
    </plugin>
    <plugin>
      <groupId>com.github.spotbugs</groupId>
      <artifactId>spotbugs-maven-plugin</artifactId>
    </plugin>
  </plugins>
</build>

Remember: pluginManagement configures plugin defaults. The plugin still needs to be present under plugins or bound by parent inheritance depending on your design.


25. Architecture Checks as Tests

Some quality rules are better expressed as tests than Maven plugin config.

Example with ArchUnit-style thinking:

class ArchitectureTest {
    @Test
    void domainMustNotDependOnInfrastructure() {
        // assert packages under domain do not access adapter/persistence packages
    }
}

Run with Surefire as unit test.

Why?

  • rules are code,
  • easier to express domain-specific architecture,
  • failure is readable,
  • versioned with module.

Use Maven plugins for generic quality. Use tests for domain-specific architecture invariants.


26. What Not to Put in Maven Quality Gate

Do not put everything in PR blocking gate.

Usually not good as mandatory PR gate:

  • long-running full SAST across entire monorepo,
  • expensive performance analysis,
  • broad duplicate detection over legacy code,
  • low-confidence style preferences,
  • manually reviewed report-only tools,
  • checks requiring flaky external service.

Better split:

GateChecks
PR gatecompile, unit tests, formatting, high-signal quality checks
merge/main gatefull verify, integration tests, stricter analysis
nightlyexpensive static analysis, broad duplication, deep security scan
releasemandatory governance, SBOM, signing, vulnerability policy

This keeps PR feedback fast without weakening release governance.


27. Failure Playbooks

Case 1: Checkstyle fails locally but not in IDE

Check:

- IDE formatter differs from Maven formatter
- import order settings differ
- line endings differ
- generated source included unexpectedly
- active Maven profile changes configLocation

Fix:

mvn spotless:apply
mvn checkstyle:check

Case 2: PMD reports thousands of old violations

Check:

- did you enable too many rules at once?
- did you scan generated code?
- are tests included unexpectedly?
- is baseline strategy missing?

Fix:

curate ruleset
exclude generated sources
run report-only temporarily
create bounded suppressions

Case 3: SpotBugs fails after JDK upgrade

Check:

- SpotBugs plugin supports running JDK?
- bytecode version supported?
- dependencies compiled with newer class file version?
- plugin version pinned too old?

Fix:

upgrade plugin in platform parent
run compatibility branch CI
adjust toolchain if needed

Case 4: Quality checks too slow

Check:

- generated code scanned?
- too many modules running unnecessary plugins?
- SpotBugs effort too high for PR gate?
- duplicate scanning test fixtures?
- Maven parallelism causing memory pressure?

Fix:

exclude noise
tune rule sets
split expensive checks
cache dependencies
measure before removing gates

Case 5: Everyone bypasses checks

Root cause is usually social + tooling:

- rules too noisy
- failures not actionable
- local fix not documented
- adoption too sudden
- no ownership

Fix:

reduce false positives
publish local commands
make formatter auto-fix mechanical issues
define suppression policy
make CI bypass visible

28. Review Checklist

Use this for Maven quality gate review:

[ ] Are plugin versions pinned in parent pluginManagement?
[ ] Are rule files versioned and reviewable?
[ ] Are generated sources excluded where appropriate?
[ ] Are checks bound to sensible lifecycle phases?
[ ] Does mvn verify run the expected quality gates?
[ ] Are skip flags explicit and blocked in protected CI?
[ ] Are reports published as CI artifacts or annotations?
[ ] Are suppressions narrow and justified?
[ ] Are formatter and Checkstyle not conflicting?
[ ] Are legacy violations handled with baseline strategy?
[ ] Are slow checks measured and tuned?
[ ] Are rules owned by a team/guild/platform group?
[ ] Can developers reproduce each failure locally?
[ ] Are quality rules different where test/generated code needs different treatment?

29. Senior-Level Summary

Code quality checks in Maven are not about making the build “strict” for its own sake. They are about creating a trustworthy feedback system.

The correct mental model:

Formatter = mechanical consistency.
Checkstyle = style and readability policy.
PMD = source-level suspicious patterns.
SpotBugs = bytecode-level likely defects.
Suppressions = controlled exceptions, not hiding place.
CI reports = operational visibility.

The strongest quality gate is not the one with the most rules. It is the one with the highest signal-to-noise ratio.

A senior Maven setup makes this easy:

mvn verify

should answer:

Does this code compile, test, package, and satisfy our minimum quality contract?

If the answer is no, the developer should know exactly what failed, why it matters, and how to fix or justify it.

That is Maven quality governance done correctly.

Lesson Recap

You just completed lesson 22 in build core. Use the series map if you want to review the broader track, or continue directly into the next lesson while the context is still warm.

Continue The Track

Keep the momentum while the lesson is still fresh. Move backward for review or continue forward into the next concept.