Skip to content

SonarQube — Quality Gates, Rules & CI Integration Guide

DodaTech Updated 2026-06-24 5 min read

In this tutorial, you'll learn about SonarQube. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

SonarQube is an open-source platform for continuous inspection of code quality that performs Static Analysis to detect bugs, vulnerabilities, code smells, and security hotspots across 30+ programming languages.

What You'll Learn

Why It Matters

Code quality degrades invisibly with every commit — duplicated logic, security vulnerabilities, and architectural issues accumulate until the codebase becomes unmaintainable. SonarQube automates code review by catching issues before they reach production. DodaTech blocks any pull request that fails SonarQube quality gates, reducing production incidents caused by code quality issues by 90%.

Real-World Use

DodaZIP's CI pipeline runs SonarQube analysis on every pull request. If the new code introduces a blocker-level bug or drops test coverage below 80%, the pipeline fails immediately. The SonarQube dashboard provides the team with a clear view of Technical Debt trends, helping them prioritize refactoring work.

flowchart LR
    A[Developer Push] --> B[CI Pipeline]
    B --> C[SonarScanner Analyze]
    C --> D[SonarQube Server]
    D --> E[Quality Gate Evaluation]
    E --> F{PASS?}
    F -->|YES| G[Merge Allowed]
    F -->|NO| H[Pipeline Fails]
    H --> I[Developer Fixes Issues]
    I --> A
    D --> J[Quality Profile]
    D --> K[Technical Debt Tracking]
    style D fill:#CB2029,color:#fff
â„šī¸ Info

Prerequisites: A running SonarQube instance (Docker or standalone). Familiarity with CI/CD pipelines in your preferred platform.

Installation

# Run SonarQube with Docker
docker run -d \
  --name sonarqube \
  -p 9000:9000 \
  -e SONAR_JDBC_URL=jdbc:postgresql://db:5432/sonarqube \
  -e SONAR_JDBC_USERNAME=sonarqube \
  -e SONAR_JDBC_PASSWORD=sonarqube \
  -v sonarqube_data:/opt/sonarqube/data \
  -v sonarqube_extensions:/opt/sonarqube/extensions \
  -v sonarqube_logs:/opt/sonarqube/logs \
  sonarqube:community-10.6.0

# Expected output:
# Container ID returned

# Access SonarQube at http://localhost:9000 (admin/admin)

# Verify SonarQube is running
curl -u admin:admin http://localhost:9000/api/system/status

# Expected output:
# {"id":"20260624100000","version":"10.6.0.92100","status":"UP"}

Configuration

# Change default admin password
curl -u admin:admin -X POST \
  http://localhost:9000/api/users/change_password \
  -d "login=admin&password=NewSecureP@ss1&previousPassword=admin"

# Create a project token
curl -u admin:NewSecureP@ss1 -X POST \
  http://localhost:9000/api/user_tokens/generate \
  -d "name=dodatech-ci"

# Expected output:
# {"login":"admin","name":"dodatech-ci","token":"sqa_abc123def456","createdAt":"2026-06-24T10:00:00+0000"}

Quality Gate Configuration

<!-- Default Quality Gate — configure via UI or API -->
<!-- Settings: SonarQube > Quality Gates > Create -->
<qualityGate>
  <name>DodaTech Production Gate</name>
  <conditions>
    <!-- Coverage must be >= 80% for new code -->
    <condition>
      <metric>new_coverage</metric>
      <operator>LT</operator>
      <error>80.0</error>
    </condition>
    <!-- Blocker and Critical issues not allowed on new code -->
    <condition>
      <metric>new_blocker_issues</metric>
      <operator>GT</operator>
      <error>0</error>
    </condition>
    <condition>
      <metric>new_critical_issues</metric>
      <operator>GT</operator>
      <error>0</error>
    </condition>
    <!-- Duplicated lines on new code < 3% -->
    <condition>
      <metric>new_duplicated_lines_density</metric>
      <operator>GT</operator>
      <error>3.0</error>
    </condition>
    <!-- Maintainability rating on new code must be A or B -->
    <condition>
      <metric>new_code_smells</metric>
      <operator>GT</operator>
      <error>0</error>
    </condition>
  </conditions>
</qualityGate>
# Create quality gate via API
curl -u admin:NewSecureP@ss1 -X POST \
  http://localhost:9000/api/qualitygates/create \
  -d "name=DodaTech Production Gate"

# Set conditions
curl -u admin:NewSecureP@ss1 -X POST \
  http://localhost:9000/api/qualitygates/create_condition \
  -d "gateName=DodaTech Production Gate&metric=new_coverage&operator=LT&error=80.0"

curl -u admin:NewSecureP@ss1 -X POST \
  http://localhost:9000/api/qualitygates/create_condition \
  -d "gateName=DodaTech Production Gate&metric=new_blocker_types&operator=GT&error=0"

# Set as default
curl -u admin:NewSecureP@ss1 -X POST \
  http://localhost:9000/api/qualitygates/set_as_default \
  -d "name=DodaTech Production Gate"

Quality Profiles

<!-- SonarQube > Quality Profiles > Create -->
<profile>
  <name>DodaTech Java 21</name>
  <language>java</language>
  <parent>Sonar way</parent>
  <rules>
    <!-- Activate additional rules -->
    <activation>
      <rule>java:S1128</rule><!-- Unused imports -->
      <severity>MAJOR</severity>
    </activation>
    <activation>
      <rule>java:S1130</rule><!-- Unnecessary throws -->
      <severity>MAJOR</severity>
    </activation>
    <activation>
      <rule>java:S107</rule><!-- Methods with > 7 params -->
      <severity>CRITICAL</severity>
    </activation>
    <!-- Deactivate noisy rules -->
    <deactivation>
      <rule>java:S1213</rule><!-- Too many var declarations -->
    </deactivation>
  </rules>
</profile>

CI/CD Integration

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

on:
  pull_request:
    branches: [main]

jobs:
  sonarqube:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - uses: actions/setup-java@v3
        with:
          distribution: 'temurin'
          java-version: '21'

      - name: Cache SonarQube packages
        uses: actions/cache@v3
        with:
          path: ~/.sonar/cache
          key: ${{ runner.os }}-sonar

      - name: Run SonarQube analysis
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
          SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
        run: |
          mvn verify sonar:sonar \
            -Dsonar.host.url=$SONAR_HOST_URL \
            -Dsonar.login=$SONAR_TOKEN \
            -Dsonar.qualitygate.wait=true \
            -Dsonar.qualitygate.timeout=300
# .gitlab-ci.yml — SonarQube scan job
sonarqube-check:
  stage: test
  image: maven:3.9-eclipse-temurin-21
  variables:
    SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar"
    GIT_DEPTH: 0
  cache:
    key: "${CI_JOB_NAME}"
    paths:
      - .sonar/cache
  script:
    - mvn verify sonar:sonar
      -Dsonar.host.url=${SONAR_HOST_URL}
      -Dsonar.login=${SONAR_TOKEN}
      -Dsonar.qualitygate.wait=true
  allow_failure: false
  only:
    - merge_requests
    - main

SonarScanner Configuration

# sonar-project.properties
sonar.projectKey=com.dodatech:user-service
sonar.projectName=User Service
sonar.projectVersion=1.0.0

sonar.sources=src/main/java
sonar.tests=src/test/java
sonar.java.binaries=target/classes

sonar.sourceEncoding=UTF-8
sonar.java.coveragePlugin=jacoco
sonar.coverage.jacoco.xmlReportPaths=target/site/jacoco/jacoco.xml

sonar.exclusions=**/generated/**,**/model/dto/**

sonar.qualitygate=DodaTech Production Gate

sonar.links.homepage=https://dodatech.com
sonar.links.ci=https://ci.dodatech.com
sonar.links.scm=https://github.com/dodatech/user-service

Common Configuration Mistakes

  1. Not setting sonar.qualitygate.wait=true in CI: Without this flag, the CI pipeline finishes before the quality gate evaluation completes, allowing failing code to merge.

  2. Ignoring new code vs overall code metrics: Quality gates should target new_* metrics (new code) rather than overall metrics. Fixing existing code debt is a separate effort.

  3. Not excluding generated code: Framework-generated code (Swagger models, JOOQ records, generated mappers) produces false positives. Add them to sonar.exclusions.

  4. Missing fetch-depth: 0 in Git checkout: SonarQube needs full Git history for blame data and new code detection. Shallow clones result in incomplete analysis.

  5. Using a single quality profile for all projects: Polyglot repositories need different rules per language. Create dedicated quality profiles per language and technology stack.

Practice Questions

  1. What is a quality gate in SonarQube? Answer: A quality gate is a set of boolean conditions that evaluates whether a project meets quality standards. It passes or fails based on metrics like coverage, issues, and duplication.

  2. How does SonarQube differentiate new code from overall code? Answer: The new code period is defined as code changed since the last analysis or within the configured number of days (default 30). Metrics prefixed with new_* track only this period.

  3. What is a quality profile? Answer: A quality profile is a set of activated rules with severity levels for a specific language. Projects inherit profiles, and custom profiles can extend or override the default Sonar way.

  4. How do you block PRs that fail quality gates? Answer: Use sonar.qualitygate.wait=true in the scanner, which blocks until evaluation completes. The CI pipeline fails if the gate fails, preventing the PR from merging.

Challenge

Set up a complete SonarQube quality enforcement pipeline: install SonarQube with PostgreSQL, create a custom quality gate (80% coverage, zero blockers/critical issues on new code, under 3% duplication), configure quality profiles for Java and JavaScript, integrate SonarScanner into a GitHub Actions or GitLab CI workflow, exclude generated code, enable PR decoration with annotations on failing lines, and enforce that the quality gate blocks failed PRs from merging.

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro