Skip to main content

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 in build.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.properties file 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.exclusions to 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.properties doesn’t collide with another project’s key in the organisation.
  • Any explicit sonar.sources/sonar.tests paths 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.reportPaths for 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.

This page was last reviewed on 15 September 2026. It needs to be reviewed again on 15 March 2027 by the page owner platops-build-notices .