SonarQube Quality Gates: Enforce Code Standards on Every PR

SonarQube Quality Gates: Enforce Code Standards on Every PR

A Quality Gate is a pass/fail threshold in SonarQube — it answers the question "is this code good enough to merge?" You configure conditions (e.g., "no new critical bugs", "coverage on new code ≥ 80%") and the gate either passes or fails the analysis. Integrate the gate status into your CI/CD pipeline and you can block merges automatically when code quality drops.

What Is a Quality Gate?

A Quality Gate is a set of conditions applied to your SonarQube analysis results. After each scan, SonarQube evaluates every condition and marks the gate as Passed or Failed.

Common conditions:

  • Coverage on new code ≥ 80%
  • Duplicated lines on new code < 3%
  • Maintainability Rating on new code = A
  • Reliability Rating on new code = A
  • Security Rating on new code = A
  • New critical/blocker issues = 0

The key phrase is "on new code" — by default, SonarQube's quality gates apply to code introduced since the previous clean analysis (the "new code period"), not the entire codebase. This makes quality gates actionable for existing projects that have accumulated technical debt.

The Default "Sonar Way" Gate

SonarQube ships with a built-in gate called Sonar Way. As of SonarQube 10, Sonar Way conditions are:

Condition Threshold
Coverage on new code ≥ 80%
Duplicated lines on new code < 3%
Maintainability Rating on new code A
Reliability Rating on new code A
Security Rating on new code A
Security Hotspots Reviewed on new code ≥ 100%

You can use Sonar Way as-is or create a custom gate based on your team's standards.

Creating a Custom Quality Gate

Go to Quality Gates in the SonarQube UI:

  1. Click Create and give it a name (e.g., "Team Standard")
  2. Add conditions:
    • Choose On New Code or Overall Code
    • Pick the metric (e.g., Coverage, Blocker Issues, Critical Issues)
    • Set the operator and threshold
  3. Set it as the default gate for new projects

For a team starting out, a stricter-than-Sonar-Way gate might look like:

Condition Threshold
Coverage on new code ≥ 85%
New blocker issues = 0
New critical issues = 0
Security Rating on new code A

Assigning Quality Gates to Projects

Each project can use a different gate:

  1. Go to your project → Project Settings → Quality Gate
  2. Select Always use a specific Quality Gate
  3. Choose your gate

Or set the default gate that all new projects inherit under Administration → Quality Gates → select gate → Set as Default.

Integrating with CI/CD

The most important integration: make your pipeline fail when the quality gate fails.

GitHub Actions

# .github/workflows/sonarqube.yml
name: SonarQube Analysis

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  sonarqube:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # Required for new code detection

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install dependencies and run tests
        run: |
          npm ci
          npm test -- --coverage

      - name: SonarQube Scan
        uses: sonarsource/sonarqube-scan-action@master
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
          SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}

      - name: Check Quality Gate
        uses: sonarsource/sonarqube-quality-gate-action@master
        timeout-minutes: 5
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
          SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}

The sonarqube-quality-gate-action step polls the SonarQube server until the analysis completes, then exits with a non-zero code if the gate fails — causing the pipeline to fail.

GitLab CI

sonarqube-analysis:
  image: sonarsource/sonar-scanner-cli:latest
  stage: test
  variables:
    SONAR_HOST_URL: $SONAR_HOST_URL
    SONAR_TOKEN: $SONAR_TOKEN
  script:
    - sonar-scanner
      -Dsonar.projectKey=$CI_PROJECT_NAME
      -Dsonar.sources=.
      -Dsonar.qualitygate.wait=true  # Block until gate is evaluated
  allow_failure: false  # Fail the pipeline on gate failure

The sonar.qualitygate.wait=true property makes the scanner block until the quality gate is evaluated, then return a non-zero exit code if it fails.

Jenkins

pipeline {
  agent any
  stages {
    stage('Test') {
      steps {
        sh 'npm test -- --coverage'
      }
    }
    stage('SonarQube Analysis') {
      environment {
        SCANNER_HOME = tool 'SonarScanner'
      }
      steps {
        withSonarQubeEnv('SonarQube') {
          sh "${SCANNER_HOME}/bin/sonar-scanner"
        }
      }
    }
    stage('Quality Gate') {
      steps {
        timeout(time: 5, unit: 'MINUTES') {
          waitForQualityGate abortPipeline: true
        }
      }
    }
  }
}

The waitForQualityGate abortPipeline: true step waits for SonarQube's webhook callback and aborts the pipeline if the gate fails.

Setting Up the Webhook (Required for Jenkins/Self-Hosted)

For the quality gate check to work synchronously in CI, SonarQube must call back to your CI server when analysis finishes.

In SonarQube: Administration → Configuration → Webhooks → Create

Name: Jenkins
URL: http://your-jenkins-host/sonarqube-webhook/
Secret: (leave blank or set a secret for verification)

For GitHub Actions, the sonarqube-quality-gate-action handles this via polling — no webhook needed.

Understanding Quality Gate Conditions

Metric Categories

Reliability — based on bug count:

  • Rating A = 0 bugs
  • Rating B = at least 1 minor bug
  • Rating C = at least 1 major bug
  • Rating D = at least 1 critical bug
  • Rating E = at least 1 blocker bug

Security — based on vulnerability count (same A-E scale)

Maintainability — based on technical debt ratio (debt time / development time):

  • A = ≤ 5%
  • B = 6–10%
  • C = 11–20%
  • D = 21–50%
  • E = > 50%

Coverage — line and branch coverage percentage from your test reports

Duplications — percentage of duplicated code blocks

New Code vs Overall Code

Quality gates have two scopes:

  • On New Code: Conditions apply only to code changed since the reference branch or period. Best for incremental improvement — you can't fail for old debt.
  • Overall Code: Conditions apply to the entire codebase. Useful for greenfield projects or when you've cleaned up legacy debt.

Most teams use On New Code conditions. This lets you have a low-quality legacy codebase while enforcing strict standards on all new contributions.

Practical Configuration Examples

Startup Team (Getting Started)

Be strict on security, permissive on coverage:

New blocker issues = 0
New critical issues = 0
Security Rating on new code = A
Security Hotspots Reviewed on new code ≥ 100%
Coverage on new code ≥ 60%

Established Engineering Team

Higher coverage bar, no regressions:

Coverage on new code ≥ 80%
Maintainability Rating on new code = A
Reliability Rating on new code = A
Security Rating on new code = A
New blocker issues = 0
Duplicated lines on new code < 3%

Financial/Healthcare (High Compliance)

Every issue must be reviewed:

Coverage on new code ≥ 90%
New issues = 0 (any severity)
Security Rating on new code = A
Security Hotspots Reviewed = 100%

Reading Quality Gate Failures

When a gate fails, the SonarQube dashboard shows which conditions failed:

Quality Gate FAILED

✗ Coverage on New Code: 62.3% (required ≥ 80%)
✗ New Blocker Issues: 1 (required = 0)
✓ Reliability Rating on New Code: A
✓ Security Rating on New Code: A

Click the failed condition to see exactly which files and lines are responsible. For coverage failures, SonarQube shows the uncovered new lines in red. For issue failures, you see the specific rule violations.

Common Mistakes

Setting thresholds too high too early — a 90% coverage gate on a codebase that currently has 40% coverage will block every PR. Start at your current level + 5%, raise it quarterly.

Ignoring Security Hotspots — hotspots aren't vulnerabilities (they need human review), but they still need to be marked "Reviewed". Teams often miss this condition and are surprised when the gate fails despite no "real" vulnerabilities.

Not setting fetch-depth: 0 — in GitHub Actions, the default shallow clone means SonarQube can't determine what's new code. Always use fetch-depth: 0.

Gate not blocking merges — configuring the gate in SonarQube isn't enough. You also need branch protection rules in GitHub/GitLab that require the SonarQube check to pass before merging.

Enforcing Gates in GitHub

Add a required status check in GitHub:

  1. Go to repository → Settings → Branches → Add rule
  2. Enable Require status checks to pass before merging
  3. Search for and add SonarQube (or whatever the check is named in your Actions workflow)

Now PRs that fail the quality gate can't be merged without a manual override — exactly the enforcement quality gates are designed for.

Read more

Start now free