How to Integrate SonarQube for Code Coverage in CI
SonarQube gives a CI pipeline a consistent way to inspect code quality, security findings, duplication and test coverage. It does not run unit tests or calculate coverage itself. Instead, your build tool executes the tests, a coverage library creates a report, and the SonarQube scanner imports that report during analysis.
That distinction matters when a pipeline appears to pass tests but SonarQube shows zero coverage. The usual cause is a missing report, an incorrect file path, or a scanner step running before the coverage file has been generated. A reliable integration treats testing, reporting and static analysis as connected stages.
The examples here focus on Java projects built with Maven, with concepts that also apply to Gradle, Groovy and REST API test suites. The same approach can run in Jenkins, GitHub Actions, GitLab CI or another continuous integration platform.
Australian engineering teams often work across Sydney, Melbourne, Brisbane and Perth, with hybrid schedules and AEST or AEDT differences affecting notifications and release windows. A central quality gate helps everyone inspect the same evidence without relying on a developer’s local machine.
Why SonarQube Belongs in the CI Pipeline
SonarQube analyses source code and test metadata in a repeatable environment. A typical Java pipeline compiles the application, runs unit and integration tests, creates a JaCoCo XML report, and then invokes the Sonar scanner. SonarQube combines that report with its own analysis of bugs, vulnerabilities, code smells, duplication and maintainability.
Coverage is usually displayed as line coverage and branch coverage. Line coverage indicates whether executable lines ran during tests, while branch coverage shows how well conditional paths were exercised. A high percentage does not prove that an API behaves correctly, so coverage should be considered alongside assertions, contract tests, mutation testing and production-like integration checks.
A quality gate turns those measurements into a decision. For example, a team can require 80 per cent coverage on new code, zero new blocker issues and a limited duplication percentage. Applying the gate to new code is often more practical than demanding an immediate improvement across an old codebase.
Prepare Tests and Coverage Reports
For Maven, JaCoCo should attach to the test phase and produce an XML report before SonarQube runs. The XML format is important because current SonarQube analysis uses it for imported coverage data. A basic plugin configuration can look like this:
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.12</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>verify</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
After mvn clean verify, the expected file is commonly found at target/site/jacoco/jacoco.xml. Confirm that the file exists in CI rather than assuming the local Maven lifecycle behaves identically. If integration tests use a separate module or profile, configure JaCoCo to include those tests deliberately and avoid accidentally overwriting the unit-test execution data.
Test reports are a separate concern. Maven Surefire reports unit-test results, while Failsafe is commonly used for integration tests. SonarQube can use those reports to understand test execution, but they do not replace JaCoCo coverage data. A pipeline should preserve both types of output as build artefacts when diagnosing failures.
For Groovy projects using Spock, the same principle applies. Spock tests must execute successfully, JaCoCo must instrument the relevant classes, and the generated XML must be available to the scanner. Exclude generated sources, configuration classes or intentionally untestable adapters only when the exclusion is documented and reviewed.
Connect the Scanner to Your Build
The simplest Maven command places the Sonar analysis after verification:
mvn clean verify sonar:sonar \
-Dsonar.host.url="$SONAR_HOST_URL" \
-Dsonar.token="$SONAR_TOKEN" \
-Dsonar.projectKey="orders-service"
The scanner can usually discover the standard JaCoCo path automatically. For a non-standard layout, set the path explicitly in project configuration:
sonar.coverage.jacoco.xmlReportPaths=\
target/site/jacoco/jacoco.xml
Do not commit authentication tokens to pom.xml, repository variables or shell scripts. Store them as protected CI secrets, restrict their permissions, and mask them in logs. A self-hosted SonarQube instance can be useful where source code must remain within a controlled environment; SonarCloud may suit teams that prefer a managed service.
| Integration approach | Strength | Common risk | Suitable use |
|---|---|---|---|
| Maven scanner | Uses the existing Java build lifecycle | Analysis can be skipped if the command stops after tests | Java and Groovy Maven projects |
| Gradle Sonar plugin | Fits multi-module Gradle builds | Report paths can differ between modules | Large JVM repositories |
| Standalone SonarScanner CLI | Works across languages and build systems | Requires careful ordering and configuration | Polyglot repositories |
| CI platform task | Easy setup in some hosted pipelines | Task defaults may hide scanner behaviour | Teams standardising on one CI provider |
| Docker-based scanner | Reproducible runtime and dependencies | Network and certificate configuration need attention | Controlled or containerised runners |
In a pull request pipeline, pass the branch or pull-request properties required by your SonarQube edition and CI provider. Keep the analysis close to the commit being reviewed so developers see findings while the change is still easy to amend. For a default branch build, publish the full project analysis and enforce the quality gate before deployment.
Build Reliable Quality Gates
A quality gate should reflect the risk of the service rather than an arbitrary number. A public REST API handling customer records may need stricter conditions for security hotspots and branch coverage than an internal command-line utility. New-code conditions are a sensible starting point for an existing repository because they prevent further degradation while the team gradually improves older areas.
A pipeline can wait for SonarQube’s result before moving to packaging or deployment. With the Maven scanner, this commonly uses sonar.qualitygate.wait=true, although it increases pipeline duration. Another approach is to submit the analysis and use a later CI step or webhook to retrieve the gate status. The important point is that the deployment decision must use the server’s completed result, not merely the scanner process exit code.
Useful checks for a backend service include:
- Generate JaCoCo XML after unit and integration tests, then verify the file path before scanning.
- Set separate expectations for overall coverage and coverage on new code.
- Import Surefire and Failsafe results so failed or skipped tests remain visible.
- Analyse pull requests with the correct repository, branch and commit metadata.
- Keep
SONAR_TOKENand server credentials in protected CI secret storage. - Exclude generated code only through reviewed, version-controlled configuration.
- Archive scanner logs, coverage reports and test results for repeatable troubleshooting.
For Australian organisations, privacy and audit requirements should influence test-data design. The Privacy Act 1988 and its Australian Privacy Principles make production personal information a poor choice for repeatable CI tests. Use synthetic names, addresses and identifiers instead. Financial services teams may also need evidence aligned with APRA CPS 234 controls, including clear ownership of security and testing processes.
Keep Feedback Fast and Useful
A full SonarQube scan does not need to run for every local test execution. Developers can run unit tests and a quick static check locally, while CI performs the authoritative scan on every pull request and default-branch build. Cache Maven dependencies and reuse compiled outputs where the CI platform supports it, but never cache a coverage report between unrelated commits.
Monorepos require extra care. Each module should publish coverage in a predictable location, and the scanner configuration must aggregate reports without counting the same classes twice. For microservices, separate SonarQube projects can make ownership and quality gates clearer. A shared dashboard can still show the state of the wider platform.
Choose runners and hosting with operational constraints in mind. An Australian company may place a self-managed SonarQube server or related build infrastructure in an AWS Sydney or Azure Australia region when residency expectations matter, while teams in other sectors may use a managed service. Check the provider’s data handling terms, especially when source code, issue details or branch names could contain confidential information.
The most valuable metric is actionable feedback. If a failed gate offers dozens of low-priority findings, developers may learn to ignore it. Prioritise new vulnerabilities, reliability defects, uncovered decision paths and meaningful duplication. Review the ruleset periodically as Java versions, frameworks and organisational risks change.
A practical setup is complete when a clean CI run can demonstrate four facts: tests executed, coverage XML was created, SonarQube imported it, and the quality gate controlled the next pipeline stage. For a Maven service, that usually means running clean verify, checking target/site/jacoco/jacoco.xml, launching sonar:sonar with protected credentials, and blocking deployment when the agreed gate fails. Keep those checks visible in the pipeline definition so every engineer can trace a quality result back to a specific commit.