SonarQube â Quality Gates, Rules & CI Integration Guide
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
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
Not setting
sonar.qualitygate.wait=truein CI: Without this flag, the CI pipeline finishes before the quality gate evaluation completes, allowing failing code to merge.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.Not excluding generated code: Framework-generated code (Swagger models, JOOQ records, generated mappers) produces false positives. Add them to
sonar.exclusions.Missing
fetch-depth: 0in Git checkout: SonarQube needs full Git history for blame data and new code detection. Shallow clones result in incomplete analysis.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
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.
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.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.
How do you block PRs that fail quality gates? Answer: Use
sonar.qualitygate.wait=truein 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