Share a Checkstyle, PMD or SpotBugs report

Java static analysis has a distribution problem shaped exactly like Java test reports. The tools produce readable HTML — build/reports/checkstyle/main.html and friends — and that HTML is visible to precisely one machine.

In CI it becomes an artifact zip. In practice, the whole apparatus collapses down to a red X on a build, and the finding itself is never read.

Getting the HTML out

Under Gradle:

checkstyle { toolVersion = '10.17.0' }

// SpotBugs writes XML by default — HTML has to be asked for.
tasks.withType(com.github.spotbugs.snom.SpotBugsTask) {
  reports {
    html.required = true
    xml.required = false
  }
}
./gradlew checkstyleMain pmdMain spotbugsMain || true
# → build/reports/checkstyle/main.html
# → build/reports/pmd/main.html
# → build/reports/spotbugs/main.html

Under Maven, the site lifecycle collects all three into target/site/.

Note the || true. These tasks fail the build on violations, which is usually what you want and always breaks a publish step that comes after them.

Publish it

curl -fsS -X PATCH "https://commareports.com/api/v1/reports/$CHECKSTYLE_REPORT_ID" \
  -H "Authorization: Bearer $COMMA_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d "$(jq -n --rawfile html build/reports/checkstyle/main.html \
        --arg title "Checkstyle — $(git rev-parse --short HEAD)" \
        '{title: $title, html: $html}')"

Each of these is a single self-contained file, which is the shape that publishes in one call — no assets to upload. Use a scoped token (reports:write) from CI secrets, and PATCH a saved id per tool so each keeps one permanent URL with a revision per build. Make the step unconditional — if: always() in GitHub Actions, post { always { … } } in Jenkins. See publishing from CI.

For the Maven route, drop the whole target/site/ folder into the app and the cross-links between the three reports keep resolving.

The part that actually matters: arguing with the rule

Failing a build on an agreed rule is fine and needs no report. The findings that need a report are the other kind:

  • The rule that's wrong for this codebase. Somebody has to say so once, with a reason, somewhere the next person will find it.
  • The legacy module with 400 violations you're burning down over a quarter. That's a shared document with a trend, not a gate.
  • The finding a reviewer thinks is a false positive. Suppress with a justification, or fix — either way the decision should be attached to the finding.

Published to a URL, all three happen on the report. Select the violation, leave a thread, and it stays anchored there across builds. See commenting on HTML.

Revision diffs give the burndown honestly: two revisions, one diff, which violations actually cleared.

Who can see it

Per report: private, your team, anyone signed in at your domain, or anyone with the link. A findings report names file paths and often quotes source lines, so team or private is usually the right default (domain-gating is Enterprise). See sharing & access control.

Limits

  • Entry HTML: 5 MB. A module with thousands of violations can exceed that — scope the report to changed files or to the higher severities, which is what gets read anyway. Assets: 25 MB per file, 250 MB and 500 files total.
  • Scripts run, sandboxed (allow-scripts, no allow-same-origin), so the reports' collapsible sections keep working.
  • 60 requests/minute per token.

Try it

Comma is free — unlimited reports, unlimited commenters, unlimited revision history.

Publish a findings report →

Related