How do you handle false positives and legacy violations across these tools without disabling the gate, and how does baselining/suppression work per tool?
answer
- suppress narrowly, never failOnViolation=false
- Checkstyle SuppressionFilter / @SuppressWarnings
- SpotBugs excludeFilterFile / @SuppressFBWarnings
- PMD ruleset exclude / NOPMD
- baseline + maxAllowedViolations ratchet
basics
~20 sUse each tool's targeted suppression instead of turning failures off: Checkstyle SuppressionFilter XML or @SuppressWarnings, PMD ruleset excludes or @SuppressWarnings("PMD.x"), SpotBugs excludeFilterFile or @SuppressFBWarnings. For legacy code, ratchet with a baseline or maxAllowedViolations so only new violations fail.
solid answer
~40 sThe wrong fix for a false positive is failOnViolation=false — that kills the whole gate. The right fix is scoped suppression: Checkstyle uses a SuppressionFilter XML (match by file/line/check) or @SuppressWarnings; PMD uses ruleset <exclude> entries, suppressMarker comments, or @SuppressWarnings("PMD.Rule"); SpotBugs uses an excludeFilterFile (match by bug pattern/class) or the @SuppressFBWarnings annotation; Spotless uses spotless:off/spotless:on toggle comments. For a large legacy codebase you 'baseline' the existing debt so only NEW violations fail: SpotBugs supports maxAllowedViolations, and the common pattern is a committed baseline/exclude file generated once, then ratcheted down over time. This keeps enforcement strict for new code while not blocking on a sea of pre-existing findings.
code
xml · 6 lines<FindBugsFilter>
<Match>
<Bug pattern="EI_EXPOSE_REP"/>
<Class name="com.acme.LegacyDto"/>
</Match>
</FindBugsFilter>go deeper
Suppress the specific finding; don't turn the whole check off.
Knows each tool's suppression file/annotation and the difference from global disabling.
Introduces baselining/ratchet to adopt tools on legacy code and keeps suppressions justified.
Owns the governance: review policy for suppressions, baseline shrink targets, and consistent mechanisms across repos.
## Principle: suppress narrowly, never disable globally A false positive or an irrelevant rule should be silenced for the *specific* location, not by turning off the fail flag for the whole project. Disabling enforcement is the most common way static-analysis gates silently rot. ## Per-tool suppression mechanisms - **Checkstyle**: a **SuppressionFilter** XML referenced via `<suppressionsLocation>`, matching by `files`/`lines`/`checks`; inline `// CHECKSTYLE:OFF/ON`; or `@SuppressWarnings("checkstyle:RuleName")` with the SuppressWarningsFilter enabled. - **PMD**: `<exclude>` rules in the ruleset XML, an `excludeFromFailureFile`, the configurable `suppressMarker` (`// NOPMD`), or `@SuppressWarnings("PMD.UnusedLocalVariable")`. - **SpotBugs**: an **excludeFilterFile** (XML `<Match>` by `Bug pattern=`, `Class`, `Method`), or the `@SuppressFBWarnings(value="...")` annotation (needs the spotbugs-annotations dependency). - **Spotless**: wrap regions with `// spotless:off` / `// spotless:on` to skip formatting; or use `<includes>/<excludes>` to scope files. - **Error Prone**: `@SuppressWarnings("CheckName")` or globally demote via `-Xep:CheckName:OFF`. ## Baselining legacy debt (the ratchet) When you adopt a tool on an old codebase, thousands of findings would block every build. Solutions: - **SpotBugs**: `maxAllowedViolations` set to the current count, then lower it over time; combined with a committed exclude file for known issues. - **Checkstyle/PMD**: commit a generated suppressions/exclude file capturing the current state so only *new* violations fail; periodically regenerate to shrink it. - General pattern: a 'baseline' file is the snapshot of accepted existing violations; new code can't add to it, and you 'ratchet' the threshold downward each sprint. ```xml <!-- SpotBugs exclude filter: ignore one pattern in generated code --> <FindBugsFilter> <Match> <Class name="~.*\.generated\..*"/> </Match> <Match> <Bug pattern="EI_EXPOSE_REP"/> <Class name="com.acme.LegacyDto"/> </Match> </FindBugsFilter> ``` ## Governance Every suppression should be justified (comment / reason) and reviewable, ideally with an owner and an expiry intent. A growing exclude file is a smell; the ratchet should trend toward zero.
- A teammate fixes a false positive by setting failOnViolation=false. Why push back?It disables the gate for the entire module, so all real violations now pass silently. The correct fix is a scoped suppression for just that finding.
- How do you adopt a strict ruleset on a huge legacy module without blocking everyone?Baseline existing violations (exclude file / maxAllowedViolations at the current count), enforce that new code adds none, and ratchet the baseline down over time.
saying these in an interview costs you the question
- Disabling failOnViolation to silence one false positive
- Letting the suppression/exclude file grow unbounded with no justifications
- Using the same suppression syntax for all tools — each has its own mechanism