SonarCloud Code Quality and Security Scanning
SonarCloud is the static code analysis platform used across CNP for code quality, maintainability, and security scanning. HMCTS has an organisation set up there (sign in with your GitHub account) — you’ll sometimes see it referred to internally as “SonarQube” too, since SonarCloud is the hosted SaaS version of the same SonarQube product; both names refer to the same scans and the same organisation.
Scanning runs automatically as part of the shared CNP Jenkins pipeline — there’s no separate tool to install or trigger by hand.
Getting started
Project setup
Per-project scan settings live in your repository, not in your Jenkinsfile. Where they go depends on your build tool:
- Java (Gradle): a
sonarqube { properties { ... } }block inbuild.gradle. The spring-boot-template shows the minimum needed:
sonarqube {
properties {
property "sonar.projectName", "Reform :: my-project"
property "sonar.projectKey", "uk.gov.hmcts.reform:my-project"
}
}
- Node/TypeScript (Yarn): a
sonar-project.propertiesfile at the repo root. The expressjs-template shows a working example:
sonar.projectName=my-team :: my-project
sonar.projectKey=my-team-my-project
sonar.sources=src/main
sonar.tests=src/test
sonar.test.exclusions=test/coverage/**
sonar.javascript.lcov.reportPaths=coverage/lcov.info
sonar.exclusions=src/main/app.ts,src/main/development.ts
Both templates are a good starting point if you’re setting this up on a new repository rather than writing the config from scratch.
First scan
Once your project has a Jenkinsfile_CNP set up against the shared CNP pipeline and the config above is in place, a “Sonar Scan” stage runs automatically on every pipeline run — nothing further to trigger manually.
The project is created in the HMCTS SonarCloud organisation the first time a scan runs on master. If your very first scan happens on a pull request instead (i.e. before anything has merged to master), it will fail because the project doesn’t exist yet — merge to master first, or expect that initial PR scan to fail.
CI/CD integration
Jenkins (the common case)
The shared pipeline’s sonarScan() step (in hmcts/cnp-jenkins-library) runs the scan for you — for Gradle projects it’s gradle sonarqube, for Yarn projects it’s sonar-scanner, both using the project settings above. On a pull request, the pipeline also passes PR-decoration properties (sonar.pullrequest.key/base/branch) to SonarCloud.
That alone doesn’t get you a comment on the PR, though — SonarCloud only posts PR comments for repositories that have been manually linked to a SonarCloud/GitHub connection, which isn’t set up by default. The scan itself still runs and the quality gate result still applies either way; without that link you’ll just need to check the project dashboard directly rather than the PR itself.
SonarCloud then calls back to a webhook on your Jenkins instance to report success/failure, and the pipeline proceeds or fails accordingly — see Troubleshooting if that callback isn’t arriving.
Azure DevOps
For Azure DevOps pipelines, use the shared job templates in hmcts/azure-devops-templates rather than adding SonarCloud tasks by hand — for example jobs/angularDotNetCore.yml already wires in templates/sonarCloud/prepare.yml and templates/sonarCloud/runAnalysis.yml (SonarCloudPrepare@3 / SonarCloudAnalyze@3 / SonarCloudPublish@3, against the HMCTS_SonarCloud service endpoint). A minimal consuming pipeline looks like:
resources:
repositories:
- repository: azureDevOpsTemplates
type: github
name: hmcts/azure-devops-templates
endpoint: 'GitHub connection 1'
jobs:
- template: jobs/angularDotNetCore.yml@azureDevOpsTemplates
parameters:
sonarCloudExtraProperties: $(sonarCloudExtraProperties)
Check the templates repo for the job template matching your stack, and its parameters, rather than reimplementing the SonarCloud tasks yourself.
Understanding results
SonarCloud evaluates every scan against a quality gate — a set of pass/fail conditions. Unless your project has a custom gate, it inherits SonarCloud’s default “Sonar way” gate, which centres on your new code (the lines changed in this scan), not the whole codebase — broadly: no new bugs or vulnerabilities, no new security hotspots left unreviewed, and a minimum test coverage on new code. This is why fixing a long-standing issue elsewhere in the file won’t affect your gate result, but a small new bug will.
The project dashboard always shows results — bugs, vulnerabilities, code smells, coverage and duplication with trends over time. You’ll only see a comment/check on the pull request itself if your repository has been manually linked to a SonarCloud/GitHub connection (see CI/CD integration above) — most repos aren’t, so the dashboard is the reliable place to check.
Terminology
| Term | Meaning |
|---|---|
| Bug | A coding mistake SonarCloud is confident will cause incorrect behaviour |
| Vulnerability | A confirmed, exploitable security weakness in the code |
| Security hotspot | Security-sensitive code that needs a human to review and judge whether it’s actually exploitable in context — not automatically a problem, unlike a vulnerability |
| Code smell | A maintainability issue — doesn’t break anything today, but makes the code harder to change safely |
| Technical debt | SonarCloud’s estimate of the effort needed to clear all outstanding code smells |
| Duplication | Blocks of near-identical code that could be extracted into shared logic |
| Coverage | Percentage of lines/branches exercised by your test suite |
Each project also carries three letter ratings (A best, E worst): Reliability (driven by bugs), Security (driven by vulnerabilities), and Maintainability (driven by code smells relative to lines of code). These are whole-codebase ratings, separate from the quality gate — a project can sit on a C maintainability rating overall and still pass every quality gate, because the gate only ever looks at new code.
Fixing common issues
SonarCloud groups findings into three types. The examples below are illustrative, not a full catalogue — SonarCloud’s own explanation on each specific finding (click into the issue) is always the authoritative one for that rule.
Security vulnerabilities and hotspots
Exploitable patterns, or code that needs review to confirm whether it’s exploitable. Fix at the source rather than suppressing the finding.
Hardcoded credentials — move to Key Vault (see secrets management) rather than the code:
// Flagged
const apiKey = "REPLACE-WITH-YOUR-OWN-KEY";
// Fixed
const apiKey = process.env.API_KEY;
SQL built by string concatenation — use parameterised queries so user input can’t change the query structure:
// Flagged
String query = "SELECT * FROM users WHERE id=" + userId;
// Fixed
PreparedStatement stmt = connection.prepareStatement("SELECT * FROM users WHERE id=?");
stmt.setInt(1, userId);
Code smells
Maintainability issues that aren’t bugs yet. These raise your technical debt but won’t fail a build on their own unless they push a new-code metric past the quality gate’s threshold.
Duplicated logic — extract the shared behaviour into one place:
// Flagged - the same query shape repeated
List<User> users = database.query("SELECT * FROM users WHERE status='active'");
List<Admin> admins = database.query("SELECT * FROM admins WHERE status='active'");
// Fixed
List<T> getActiveItems(Class<T> type) {
return database.query("SELECT * FROM " + type.getName() + " WHERE status='active'");
}
Deeply nested conditionals — prefer early returns:
// Flagged
if (user != null) {
if (user.isActive()) {
if (user.hasPermission("admin")) {
// do something
}
}
}
// Fixed
if (user == null || !user.isActive() || !user.hasPermission("admin")) {
return;
}
// do something
Bugs
Patterns SonarCloud is confident will cause incorrect behaviour — these are the ones most likely to actually break something in production, so prioritise them over smells.
Null dereference:
// Flagged
User user = getUserById(id);
String email = user.getEmail(); // could throw NPE
// Fixed
User user = getUserById(id);
String email = user != null ? user.getEmail() : null;
Unclosed resource (file handles, connections, streams) — use try-with-resources so it’s closed even if an exception is thrown:
// Flagged
FileInputStream in = new FileInputStream(path);
// ... use in, but an exception here leaks the handle
// Fixed
try (FileInputStream in = new FileInputStream(path)) {
// ... use in
}
If it’s a false positive
Use SonarCloud’s own “Mark as…” resolution on the issue (with a reason) rather than editing scan config to exclude it — that keeps the reasoning visible to the next person who looks at it, instead of silently hiding the code from future scans too.
Best practices
- Install SonarLint in your IDE — it flags the same rules SonarCloud will, inline as you type, so issues get caught before you even open a PR.
- Run the scanner locally before pushing where practical — for Gradle,
./gradlew --no-daemon sonarqube; for Yarn,yarn sonar-scan. Both need a personal SonarQube/SonarCloud user token first — see Running SonarQube Scan Locally for the token setup and exact steps. - Treat new findings as part of the PR, not a follow-up — the quality gate is scoped to new code specifically so this is realistic to keep on top of.
- Don’t widen
sonar.exclusionsto make a gate pass — exclude generated code and test fixtures, not code you don’t want reviewed. - If the default quality gate genuinely doesn’t fit your project (e.g. a legacy codebase you’re incrementally improving), discuss a custom gate with your team first — it’s a project-wide setting, not something to loosen for a single PR.
- Dependency vulnerabilities are a related but separate concern, handled by automated dependency updates rather than SonarCloud.
Troubleshooting
Quality gate failing and you’re not sure why Check the project dashboard (or the PR comment/check, if your repo has PR decoration set up — see Understanding results) — it links straight to the new issues that caused the fail. Fixing those and pushing again re-triggers analysis.
Pipeline hangs or times out at the Sonar scan stage This is almost always the SonarCloud→Jenkins webhook callback not arriving, not the scan itself failing.
Diagnosing the webhook delivery itself (SonarCloud’s webhook admin page, App Proxy errors, etc.) needs Platform Operations access that developers don’t have by default — raise it in #platops-help rather than trying to chase it yourself.
Scanner step itself fails (an actual scan error, not a gate failure) Distinguish this from a quality gate failure — that’s the scan reporting real findings; this is the scan not completing at all. Check:
- The project key in
build.gradle/sonar-project.propertiesdoesn’t collide with another project’s key in the organisation. - Any explicit
sonar.sources/sonar.testspaths actually exist in the repo — a mistyped path makes the scanner error rather than just find nothing. - The scanner log itself (Jenkins console output for that stage) for the specific error — scanner failures are usually explicit about what’s wrong, unlike the webhook symptom above which just hangs.
Coverage showing as 0% or missing entirely This is almost always an ordering or path problem, not SonarCloud itself:
- The coverage report has to exist before the Sonar scan stage runs — if your tests and coverage generation happen after
sonarScan()in the pipeline, or don’t run at all on that branch, there’s nothing for Sonar to read. - The report path SonarCloud is told to read (
sonar.javascript.lcov.reportPathsfor JS/TS, the Jacoco path for Gradle) has to match where your test task actually writes it — check the two agree rather than assuming the template default is right for your project layout.
Need admin access in SonarCloud (managing quality gates, project settings, etc.)
This is held by Platform Operations — ask in #platops-help rather than assuming who currently has it, since that list changes.
Something else
See Asking for help — #platops-help for support requests, #cloud-native/#sds-cloud-native for general questions.
Related documentation
- How SonarQube works on Jenkins — the operational runbook this page’s troubleshooting section is drawn from
- Pipeline libraries — how the shared Jenkins pipeline (including the Sonar Scan stage) gets set up
- Automated dependency updates — for dependency-level vulnerabilities, a separate concern from SonarCloud’s code analysis
- Secrets management — where credentials belong instead of in code