Operations 34 min read

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.

Raymond Ops
Raymond Ops
Raymond Ops
How to Use SonarQube for Code‑Quality Scanning and Eliminate Bugs

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
EOF

Install 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-16

Install 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 LTS

Core 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/sonarqube

Edit 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/temp

Create 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 sonarqube

Start 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 | jq

After 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=300

Real‑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/classes

Case 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 -Xms4g

Use 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=2

Security 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: overlay

Common 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 limit

Scan 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/validate

Database 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/

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

CI/CDDevOpscode qualitystatic analysisSonarQubeQuality Gate
Raymond Ops
Written by

Raymond Ops

Linux ops automation, cloud-native, Kubernetes, SRE, DevOps, Python, Golang and related tech discussions.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.