How to Use SonarQube for Code‑Quality Scanning and Eliminate Bugs
This article explains why code‑quality issues are a major source of technical debt, introduces SonarQube’s multi‑language static analysis, describes its core features such as multi‑dimensional analysis, incremental scans and Quality Gates, and provides step‑by‑step deployment, configuration, CI/CD integration, best‑practice recommendations, troubleshooting tips and monitoring scripts to ensure reliable, secure and scalable code‑quality management in production environments.
Overview
Code‑quality problems are a primary source of technical debt. An undetected NullPointerException can crash a production service, and a hidden SQL‑injection vulnerability can expose an entire database. Traditional code review relies on manual inspection, which is inefficient and error‑prone.
A 2024 incident at a large internet company caused a payment service outage for four hours and a loss of over 2 million CNY. The root cause was a simple NPE that existed in the commit; a SonarQube Quality Gate would have blocked the code from reaching production.
Technical Features
Multi‑dimensional analysis
Reliability : Detect bugs that may cause runtime errors.
Security : Identify security hotspots and known vulnerabilities.
Maintainability : Find code smells and assess technical debt.
Coverage : Integrate unit‑test coverage reports.
Duplications : Detect duplicated code blocks.
Incremental analysis
SonarQube can scan only the files changed in the current commit, reducing scan time from several hours (full scan) to a few minutes for large repositories.
Quality Gate
The Quality Gate defines entry criteria for code quality. If the criteria are not met, SonarQube can block merges or deployments, preventing low‑quality code from entering production.
Rich rule set
SonarQube ships with thousands of built‑in rules covering best practices for many languages. Rules are continuously updated to track new security issues and coding standards. Users can also add custom rules or import third‑party rule sets.
Applicable Scenarios
Enterprise‑level quality management : Establish unified coding standards, quantify quality metrics, track technical‑debt trends, and generate reports for management decisions.
DevSecOps – shift‑left security : Detect OWASP Top 10 issues, CWE/SANS rule violations, sensitive‑information leakage, and dependency‑vulnerability scanning early in the development flow.
CI/CD quality gate : Enforce PR/MR code‑quality inspection, block non‑compliant merges, send automated quality‑report notifications, and integrate deeply with Jenkins or GitLab CI.
Technical‑debt governance : Perform comprehensive health checks on existing code, identify high‑risk areas, prioritize refactoring, track debt repayment progress, and prevent debt accumulation.
Environment Requirements
CPU : Minimum 2 cores, recommended 8 cores – scanning is CPU‑intensive.
Memory : Minimum 4 GB, recommended 16 GB – Elasticsearch needs ample memory.
Disk : Minimum 50 GB SSD, recommended 200 GB SSD – data grows quickly.
JDK : Minimum JDK 17, recommended JDK 21 – required by SonarQube 10.x.
Database : Minimum PostgreSQL 13, recommended PostgreSQL 16 – production must use an external DB.
OS : Minimum CentOS 7 / Ubuntu 20.04, recommended Rocky Linux 9 / Ubuntu 22.04 – LTS versions are preferred.
Docker : Minimum 20.10+, recommended 24.0+ – container deployment is recommended.
SonarQube version : Minimum 10.0, recommended 10.4 LTS (released 2025).
Detailed Steps
Preparation
System initialization
# Create sonar user
useradd -m -s /bin/bash sonar
# Adjust system parameters (required by Elasticsearch)
cat >> /etc/sysctl.conf <<'EOF'
vm.max_map_count=524288
fs.file-max=131072
EOF
sysctl -p
# Adjust user resource limits
cat >> /etc/security/limits.conf <<'EOF'
sonar soft nofile 131072
sonar hard nofile 131072
sonar soft nproc 8192
sonar hard nproc 8192
EOFInstall PostgreSQL
# Rocky Linux 9 – install PostgreSQL 16
dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-9-x86_64/pgdg-redhat-repo-latest.noarch.rpm
dnf -qy module disable postgresql
dnf install -y postgresql16-server postgresql16
# Initialise database
/usr/pgsql-16/bin/postgresql-16-setup initdb
systemctl enable postgresql-16 --now
# Create SonarQube database and user
sudo -u postgres psql <<'EOF'
CREATE USER sonarqube WITH ENCRYPTED PASSWORD 'SonarQube@2025';
CREATE DATABASE sonarqube OWNER sonarqube;
GRANT ALL PRIVILEGES ON DATABASE sonarqube TO sonarqube;
\c sonarqube
GRANT ALL ON SCHEMA public TO sonarqube;
EOF
# Configure pg_hba.conf for password authentication
cat > /var/lib/pgsql/16/data/pg_hba.conf <<'EOF'
local all postgres peer
local all all peer
host sonarqube sonarqube 127.0.0.1/32 scram-sha-256
host sonarqube sonarqube ::1/128 scram-sha-256
host all all 127.0.0.1/32 scram-sha-256
host all all ::1/128 scram-sha-256
EOF
systemctl restart postgresql-16Install JDK 17
# Install OpenJDK 17
dnf install -y java-17-openjdk java-17-openjdk-devel
# Verify installation
java -version
# openjdk version "17.0.10" 2024-01-16 LTSCore Configuration
Download SonarQube 10.4 LTS
# cd /opt
wget https://binaries.sonarsource.com/Distribution/sonarqube/sonarqube-10.4.1.88267.zip
unzip sonarqube-10.4.1.88267.zip
mv sonarqube-10.4.1.88267 sonarqube
chown -R sonar:sonar /opt/sonarqubeEdit sonar.properties
# Database configuration
sonar.jdbc.username=sonarqube
sonar.jdbc.password=SonarQube@2025
sonar.jdbc.url=jdbc:postgresql://localhost:5432/sonarqube
# Web server configuration
sonar.web.host=0.0.0.0
sonar.web.port=9000
sonar.web.context=/sonar
# Elasticsearch configuration
sonar.search.javaOpts=-Xmx2g -Xms2g -XX:MaxDirectMemorySize=256m -XX:+HeapDumpOnOutOfMemoryError
# Compute Engine configuration
sonar.ce.javaOpts=-Xmx2g -Xms1g -XX:+HeapDumpOnOutOfMemoryError
# Web JVM configuration
sonar.web.javaOpts=-Xmx1g -Xms512m -XX:+HeapDumpOnOutOfMemoryError
# Logging
sonar.log.level=INFO
sonar.path.logs=/opt/sonarqube/logs
# Data directories
sonar.path.data=/opt/sonarqube/data
sonar.path.temp=/opt/sonarqube/tempCreate Systemd service
cat > /etc/systemd/system/sonarqube.service <<'EOF'
[Unit]
Description=SonarQube service
After=syslog.target network.target postgresql-16.service
[Service]
Type=forking
ExecStart=/opt/sonarqube/bin/linux-x86-64/sonar.sh start
ExecStop=/opt/sonarqube/bin/linux-x86-64/sonar.sh stop
User=sonar
Group=sonar
Restart=always
LimitNOFILE=131072
LimitNPROC=8192
TimeoutStartSec=300
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable sonarqubeStart and Verify
Start the service and check its health.
# Start SonarQube
systemctl start sonarqube
# View startup logs
tail -f /opt/sonarqube/logs/sonar.log
# Check service status
systemctl status sonarqube
# Verify port listening
ss -tlnp | grep 9000
# API health check
curl -s http://localhost:9000/sonar/api/system/status | jqAfter startup, open http://<server‑ip>:9000/sonar and log in with the default admin/admin. The system forces a password change on first login.
Example Configurations
Maven ( pom.xml )
<project>
<properties>
<sonar.host.url>http://sonarqube.example.com:9000/sonar</sonar.host.url>
<sonar.projectKey>${project.groupId}:${project.artifactId}</sonar.projectKey>
<sonar.projectName>${project.name}</sonar.projectName>
<sonar.java.source>17</sonar.java.source>
<sonar.coverage.jacoco.xmlReportPaths>${project.build.directory}/site/jacoco/jacoco.xml</sonar.coverage.jacoco.xmlReportPaths>
<sonar.exclusions>**/generated/**,**/dto/**,**/entity/**</sonar.exclusions>
</properties>
<build>
<plugins>
<!-- JaCoCo plugin for coverage -->
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.11</version>
<executions>
<execution>
<id>prepare-agent</id>
<goals><goal>prepare-agent</goal></goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals><goal>report</goal></goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>Gradle ( build.gradle )
plugins {
id "org.sonarqube" version "5.0.0.4638"
id "jacoco"
}
sonar {
properties {
property "sonar.host.url", "http://sonarqube.example.com:9000/sonar"
property "sonar.projectKey", "com.example:myproject"
property "sonar.projectName", "My Project"
property "sonar.java.source", "17"
property "sonar.coverage.jacoco.xmlReportPaths", "${buildDir}/reports/jacoco/test/jacocoTestReport.xml"
property "sonar.exclusions", "**/generated/**,**/dto/**"
}
}
jacocoTestReport {
reports { xml.required = true }
}
tasks.named('sonar') { dependsOn jacocoTestReport }Generic sonar-project.properties
# Project identification
sonar.projectKey=mycompany:myproject
sonar.projectName=My Project
sonar.projectVersion=1.0.0
# Source configuration
sonar.sources=src/main
sonar.tests=src/test
sonar.java.binaries=target/classes
sonar.java.libraries=target/dependency/*.jar
sonar.sourceEncoding=UTF-8
# Exclusions
sonar.exclusions=**/generated/**,**/test/**,**/*.min.js
sonar.coverage.exclusions=**/dto/**,**/entity/**,**/config/**
sonar.cpd.exclusions=**/dto/**,**/entity/**
# Coverage report
sonar.coverage.jacoco.xmlReportPaths=target/site/jacoco/jacoco.xml
# Quality configuration
sonar.qualitygate.wait=true
sonar.qualitygate.timeout=300Real‑World Cases
Case 1 – Multi‑module project
# Parent sonar-project.properties
sonar.projectKey=ecommerce-platform
sonar.projectName=E‑Commerce Platform
sonar.projectVersion=2.0.0
# Module definition
sonar.modules=user-service,order-service,payment-service,inventory-service
# Common configuration
sonar.sourceEncoding=UTF-8
sonar.java.source=17
# Individual module settings (example for user‑service)
user-service.sonar.projectName=User Service
user-service.sonar.sources=src/main/java
user-service.sonar.tests=src/test/java
user-service.sonar.java.binaries=target/classes
order-service.sonar.projectName=Order Service
order-service.sonar.sources=src/main/java
order-service.sonar.tests=src/test/java
order-service.sonar.java.binaries=target/classesCase 2 – GitLab CI integration
variables:
SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar"
GIT_DEPTH: "0"
stages:
- build
- test
- analysis
- deploy
build:
stage: build
image: maven:3.9-eclipse-temurin-17
script:
- mvn clean compile -DskipTests
artifacts:
paths:
- target/
expire_in: 1 hour
test:
stage: test
image: maven:3.9-eclipse-temurin-17
script:
- mvn test jacoco:report
artifacts:
paths:
- target/
reports:
junit: target/surefire-reports/*.xml
sonarqube-check:
stage: analysis
image: maven:3.9-eclipse-temurin-17
variables:
SONAR_TOKEN: "${SONAR_TOKEN}"
script:
- mvn sonar:sonar \
-Dsonar.host.url=${SONAR_HOST_URL} \
-Dsonar.login=${SONAR_TOKEN} \
-Dsonar.projectKey=${CI_PROJECT_PATH_SLUG} \
-Dsonar.projectName="${CI_PROJECT_NAME}" \
-Dsonar.qualitygate.wait=true
allow_failure: false
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == "main"
- if: $CI_COMMIT_BRANCH == "develop"
sonarqube-mr:
stage: analysis
image: maven:3.9-eclipse-temurin-17
script:
- mvn sonar:sonar \
-Dsonar.host.url=${SONAR_HOST_URL} \
-Dsonar.login=${SONAR_TOKEN} \
-Dsonar.pullrequest.key=${CI_MERGE_REQUEST_IID} \
-Dsonar.pullrequest.branch=${CI_MERGE_REQUEST_SOURCE_BRANCH_NAME} \
-Dsonar.pullrequest.base=${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"Case 3 – Custom Quality Gate via API
# Create a new gate
curl -X POST -u admin:password \
"http://sonarqube.example.com:9000/sonar/api/qualitygates/create" \
-d "name=Strict-Gate"
# Add condition: new coverage >= 80%
curl -X POST -u admin:password \
"http://sonarqube.example.com:9000/sonar/api/qualitygates/create_condition" \
-d "gateName=Strict-Gate" \
-d "metric=new_coverage" \
-d "op=LT" \
-d "error=80"
# Add condition: new bugs = 0
curl -X POST -u admin:password \
"http://sonarqube.example.com:9000/sonar/api/qualitygates/create_condition" \
-d "gateName=Strict-Gate" \
-d "metric=new_bugs" \
-d "op=GT" \
-d "error=0"
# Add condition: new vulnerabilities = 0
curl -X POST -u admin:password \
"http://sonarqube.example.com:9000/sonar/api/qualitygates/create_condition" \
-d "gateName=Strict-Gate" \
-d "metric=new_vulnerabilities" \
-d "op=GT" \
-d "error=0"
# Add condition: new duplication <= 3%
curl -X POST -u admin:password \
"http://sonarqube.example.com:9000/sonar/api/qualitygates/create_condition" \
-d "gateName=Strict-Gate" \
-d "metric=new_duplicated_lines_density" \
-d "op=GT" \
-d "error=3"Best Practices & Cautions
Performance optimisation
Configure JVM memory appropriately
# Small project (<100K LOC)
sonar.web.javaOpts=-Xmx512m -Xms256m
sonar.ce.javaOpts=-Xmx1g -Xms512m
sonar.search.javaOpts=-Xmx1g -Xms1g
# Large project (>1M LOC)
sonar.web.javaOpts=-Xmx2g -Xms1g
sonar.ce.javaOpts=-Xmx4g -Xms2g
sonar.search.javaOpts=-Xmx4g -Xms4gUse incremental analysis
# Analyse only changed files to speed up scans
mvn sonar:sonar -Dsonar.inclusions=$(git diff --name-only HEAD~1 | tr '
' ',')Set exclusion rules wisely
# Exclude generated code and minified assets
sonar.exclusions=**/generated/**,**/node_modules/**,**/*.min.js
# Exclude test code from coverage calculation
sonar.coverage.exclusions=**/test/**,**/dto/**,**/entity/**Database optimisation
# Regular PostgreSQL maintenance
VACUUM ANALYZE;
REINDEX DATABASE sonarqube;
# Connection‑pool tuning (in sonar.properties)
sonar.jdbc.maxActive=60
sonar.jdbc.maxIdle=5
sonar.jdbc.minIdle=2Security hardening – HTTPS
# Nginx reverse‑proxy example (recommended)
server {
listen 443 ssl;
server_name sonarqube.example.com;
ssl_certificate /etc/nginx/ssl/sonarqube.crt;
ssl_certificate_key /etc/nginx/ssl/sonarqube.key;
location / {
proxy_pass http://127.0.0.1:9000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}LDAP/AD authentication
# sonar.properties additions
sonar.security.realm=LDAP
ldap.url=ldap://ldap.example.com:389
ldap.bindDn=cn=sonar,ou=services,dc=example,dc=com
ldap.bindPassword=secret
ldap.user.baseDn=ou=users,dc=example,dc=com
ldap.user.request=(&(objectClass=user)(sAMAccountName={login}))
ldap.user.realNameAttribute=cn
ldap.user.emailAttribute=mail
ldap.group.baseDn=ou=groups,dc=example,dc=com
ldap.group.request=(&(objectClass=group)(member={dn}))High‑availability deployment (Docker‑Compose)
version: '3.8'
services:
sonarqube:
image: sonarqube:10.4-community
environment:
SONAR_JDBC_URL: jdbc:postgresql://postgres:5432/sonarqube
SONAR_JDBC_USERNAME: sonarqube
SONAR_JDBC_PASSWORD: ${SONAR_DB_PASSWORD}
SONAR_SEARCH_JAVAADDITIONALOPTS: "-Dnode.store.allow_mmap=false"
volumes:
- sonarqube_data:/opt/sonarqube/data
- sonarqube_extensions:/opt/sonarqube/extensions
- sonarqube_logs:/opt/sonarqube/logs
deploy:
resources:
limits:
memory: 8G
reservations:
memory: 4G
networks:
- sonarnet
depends_on:
- postgres
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: sonarqube
POSTGRES_PASSWORD: ${SONAR_DB_PASSWORD}
POSTGRES_DB: sonarqube
volumes:
- postgresql_data:/var/lib/postgresql/data
deploy:
resources:
limits:
memory: 2G
networks:
- sonarnet
volumes:
sonarqube_data:
sonarqube_extensions:
sonarqube_logs:
postgresql_data:
networks:
sonarnet:
driver: overlayCommon Pitfalls
Startup fails: max virtual memory areas – Elasticsearch requirement. sysctl -w vm.max_map_count=524288 Startup fails: cannot run as root – Security restriction. Run as non‑root user (e.g., sonar).
Database connection failure – Authentication mis‑configuration. Verify pg_hba.conf and passwords.
Scan timeout – Project too large or insufficient memory. Increase CE memory or split the project.
Quality Gate stays Pending – Webhook mis‑configuration. Verify Jenkins or GitLab callback URL.
Coverage reported as 0 – Incorrect report path. Confirm JaCoCo report location.
Chinese characters appear garbled – Encoding mismatch. Force UTF‑8 encoding in sonar.properties.
Token authentication fails – Token expired or insufficient permissions. Regenerate token and assign proper permissions.
Branch analysis fails – Community edition limitation. Upgrade to Developer edition.
Plugin installation fails – Version incompatibility. Check plugin compatibility with SonarQube version.
Fault Diagnosis & Monitoring
Service Startup Issues
# Check logs
tail -100 /opt/sonarqube/logs/sonar.log
tail -100 /opt/sonarqube/logs/es.log
tail -100 /opt/sonarqube/logs/web.log
tail -100 /opt/sonarqube/logs/ce.log
# Common checks
ss -tlnp | grep -E "9000|9001" # port usage
ps aux | grep -E "sonar|elasticsearch" # process status
ls -la /opt/sonarqube/ # permissions
sysctl vm.max_map_count # sys params
ulimit -n # file descriptor limitScan Failure Diagnosis
# Enable verbose logging
mvn sonar:sonar -X -Dsonar.verbose=true 2>&1 | tee sonar-debug.log
# Verify scanner version
sonar-scanner --version
# Test connectivity
curl -v http://sonarqube.example.com:9000/sonar/api/system/status
# Validate token
curl -u admin:password http://sonarqube.example.com:9000/sonar/api/authentication/validateDatabase Troubleshooting
# Active connections
SELECT count(*) FROM pg_stat_activity WHERE datname = 'sonarqube';
# Largest tables
SELECT schemaname, tablename, pg_size_pretty(pg_total_relation_size(schemaname || '.' || tablename)) AS size
FROM pg_tables
WHERE schemaname = 'public'
ORDER BY pg_total_relation_size(schemaname || '.' || tablename) DESC
LIMIT 20;
# Clean old data (keep 90 days)
DELETE FROM events WHERE created_at < NOW() - INTERVAL '90 days';
DELETE FROM project_measures WHERE created_at < NOW() - INTERVAL '90 days';
VACUUM ANALYZE;Performance Monitoring Script
#!/bin/bash
# sonar_monitor.sh – SonarQube performance monitor
SONAR_URL="http://localhost:9000/sonar"
ALERT_EMAIL="[email protected]"
get_system_health() {
curl -s "${SONAR_URL}/api/system/health" | jq -r '.health'
}
get_ce_queue() {
curl -s "${SONAR_URL}/api/ce/activity_status" | jq
}
check_disk_space() {
df -h /opt/sonarqube/data | awk 'NR==2 {print $5}' | tr -d '%'
}
main() {
health=$(get_system_health)
disk_usage=$(check_disk_space)
if [[ "$health" != "GREEN" ]]; then
echo "ALERT: SonarQube health is $health"
# send email or alert here
fi
if (( disk_usage > 85 )); then
echo "ALERT: Disk usage is ${disk_usage}%"
# send email or alert here
fi
echo "=== SonarQube Status Report ==="
echo "Health: $health"
echo "Disk Usage: ${disk_usage}%"
echo "CE Queue:"
get_ce_queue
}
main "$@"Prometheus Exporter Configuration
# prometheus.yml
scrape_configs:
- job_name: 'sonarqube'
metrics_path: '/sonar/api/monitoring/metrics'
static_configs:
- targets: ['sonarqube.example.com:9000']
basic_auth:
username: 'admin'
password: 'password'
# Alert rules
groups:
- name: sonarqube
rules:
- alert: SonarQubeDown
expr: up{job="sonarqube"} == 0
for: 5m
labels:
severity: critical
annotations:
summary: "SonarQube is down"
- alert: SonarQubeCEQueueHigh
expr: sonarqube_ce_queue_pending > 50
for: 15m
labels:
severity: warning
annotations:
summary: "SonarQube CE queue is high"Summary
Key Technical Takeaways
Deployment architecture: SonarQube uses a three‑tier architecture (Web Server + Compute Engine + Elasticsearch). Production requires an external PostgreSQL database and proper JVM tuning for stability.
Quality Gate: Acts as the final quality barrier; start with permissive thresholds and tighten over time.
CI/CD integration: Deep integration with Jenkins/GitLab CI enables continuous quality enforcement via webhook callbacks.
Incremental analysis: Crucial for large projects, reducing scan time from hours to minutes.
Rule management: Tailor rule sets to project needs; focus on precision rather than quantity.
Advanced Learning Paths
SonarQube Enterprise features – Portfolio management, branch analysis, PR decoration, security reports.
Custom rule development – Build custom checks using the SonarJava API.
Integration with security scanners – Combine with OWASP Dependency‑Check, Snyk, etc.
Code‑quality KPI framework – Define, measure and improve quality metrics across teams.
References
SonarQube official documentation: https://docs.sonarqube.org/latest/
SonarSource rule repository: https://rules.sonarsource.com/
SonarQube GitHub: https://github.com/SonarSource/sonarqube
JaCoCo documentation: https://www.jacoco.org/jacoco/trunk/doc/
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Raymond Ops
Linux ops automation, cloud-native, Kubernetes, SRE, DevOps, Python, Golang and related tech discussions.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
