Gating New Code, Not History: Adopting SonarQube Without a Legacy Cleanup Sprint
Introducing SonarQube to a legacy Java codebase without blocking delivery: scope Quality Gate conditions to New Code, wire JaCoCo coverage, and verify the gate actually blocks CI.
07 Dec 2025, 17:04 UTC

You inherit a Java codebase with 4,000 open issues, 31% coverage, and a mandate to "add static analysis." If you turn on SonarQube's default Quality Gate as-is, every build goes red on day one — not because anyone wrote bad code this week, but because the last eight years of code fails the bar. The useful takeaway: you don't have to fix the past to stop the bleeding. SonarQube's New Code model lets you gate only what changed, so the pipeline enforces today's standards while legacy debt gets tracked separately and burned down opportunistically.
This is the "Clean as You Code" strategy, and it's the single most practical decision when introducing SonarQube into an existing project.
What a Quality Gate actually evaluates
A Quality Gate is a set of pass/fail conditions evaluated after each analysis — things like reliability rating, security rating, coverage, and duplication. Your CI pipeline can poll the analysis result (or receive a webhook) and fail the build when the gate fails.
The key insight is that most conditions can be scoped to either Overall Code or New Code. New Code is defined by a baseline you choose per project:
- Previous version — everything since the last version string you passed to the scanner.
- Reference branch — everything differing from, say,
main. This is usually the right choice for branch-based workflows. - Number of days — anything changed in the last N days. Beware: after a quiet period, untouched code can suddenly "become new" again, which surprises teams.
Check the active baseline under Project Settings → New Code before trusting any gate result.
A gate that fails on new mistakes, not old ones
Clone the built-in "Sonar way" gate (Quality Gates → Copy) rather than editing it, then shape the copy around new-code conditions. A sensible starting set for a legacy Java service:
| Condition | Scope | Threshold |
|---|---|---|
| Reliability rating | New Code | A (no new bugs) |
| Security rating | New Code | A |
| Security Hotspots reviewed | New Code | 100% |
| Coverage | New Code | ≥ 80% |
| Duplicated lines | New Code | ≤ 3% |
| Maintainability rating | Overall Code | not worse than B (optional ratchet) |
Notice what's absent: no overall coverage condition. Demanding 80% coverage across a codebase sitting at 31% is how static analysis initiatives die in week two. Instead, every merge must cover its own changes, so the overall number climbs on its own.
Worked example: Java, Maven, JaCoCo
Coverage-on-new-code only works if the scanner receives a coverage report. For Java that typically means JaCoCo. In your CI job (run as a user with permission to execute the build and reach the SonarQube server):
mvn verify org.jacoco:jacoco-maven-plugin:report \
org.sonarsource.scanner.maven:sonar-maven-plugin:sonar \
-Dsonar.token=$SONAR_TOKEN \
-Dsonar.pullrequest.key=$PR_ID \
-Dsonar.pullrequest.branch=$BRANCH \
-Dsonar.pullrequest.base=mainReplace $SONAR_TOKEN, $PR_ID, and $BRANCH with values from your CI environment; omit the PR parameters for a plain branch analysis. Then make the pipeline actually wait for the verdict — in Jenkins that's the waitForQualityGate() step, which consumes the webhook SonarQube posts back. Without a wait step, the gate result is just a log line and nothing blocks.
To verify the setup end to end: create a throwaway branch, add one small uncovered method with an obvious code smell, open a PR, and confirm three things — the gate fails on New Code, the overall project metrics stay green, and the CI build actually goes red. Then merge a clean change and confirm it passes. Never roll this out on faith.
Trade-offs and gotchas
- Edition matters. Branch and pull request analysis — which powers PR decoration in GitHub/GitLab/Azure DevOps/Bitbucket — is not available in every edition. Community Edition historically analyzes a single branch. Confirm what your organization runs before designing a PR-centric workflow.
- Missing coverage reports can silently distort the gate. If JaCoCo output isn't found, coverage-on-new-code may fail the gate or evaluate as zero depending on version and config. Check the scanner log for the JaCoCo import step.
- Gaming is possible. Developers can keep individual changes tiny to stay under duplication thresholds, or exclude files. The gate is a floor, not a culture.
- Version drift. Condition names and defaults change between SonarQube versions and LTS releases. A gate config copied from a two-year-old tutorial may not match your UI — verify against your installed version.
Where to start Monday morning
Pick one service, set its New Code baseline to your main branch, clone the default gate with the five conditions above, wire waitForQualityGate (or your CI equivalent) into the pipeline, and run the deliberate-failure test. Once the team trusts that red means "your change," not "the 2017 codebase," you can tighten thresholds — and start scheduling legacy cleanup on your own terms instead of the gate's.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.