Integrating SonarQube with Jenkins for Continuous Code Quality Analysis
Every CI/CD pipeline has a code quality gate. Without it, you are shipping code that might compile and deploy successfully but is full of bugs, security vulnerabilities, duplicated logic, and poor practices. SonarQube is the tool that fills this gap. It scans your source code, measures its quality, and reports back everything a team needs to know before approving a deployment. In this post, we will set up SonarQube on a dedicated EC2 instance, integrate it with Jenkins, and add a code quality analysis stage to our pipeline.

What SonarQube Actually Does
SonarQube is an open-source platform for continuous code quality and security. When it scans your code, it looks for:
Bugs - code that will likely produce incorrect behavior at runtime
Vulnerabilities - security weaknesses that attackers could exploit
Code smells - maintainability issues that make the code harder to understand and modify
Duplications - blocks of identical or near-identical code repeated across the codebase
Coverage - how much of the code is covered by unit tests
After a scan, SonarQube produces a dashboard report. Each project gets a quality gate result: either Passed or Failed. A passed quality gate means the code meets the standards your team has defined. A failed quality gate signals that problems need to be fixed before the code is allowed to proceed to deployment.
SonarQube supports over 20 programming languages. It is not just for Java - Python, JavaScript, TypeScript, C#, Go, and many others are supported.
The SonarQube Architecture
SonarQube has three internal components:
SonarQube Scanner - runs in the build environment (on the Jenkins server or slave node), analyzes the source code, and submits results to the server.
SonarQube Server - receives the analysis data, processes it, applies quality gate rules, and makes results available through the dashboard.
SonarQube Database - stores all historical scan results. This is set up automatically as part of the SonarQube installation.
You interact with the SonarQube Server through its web dashboard. Jenkins runs the Scanner as part of the pipeline.
DevSecOps Connection
SonarQube is a DevSecOps tool. DevSecOps means integrating security into the development and operations process rather than treating it as a separate, final step. Running SonarQube on every commit means security vulnerabilities are caught early in the development cycle - when they are cheapest to fix.
Infrastructure Requirements
SonarQube requires at least a t2.medium instance. Do not try to run it on a t2.micro. The process will either fail to start or be so slow it becomes unusable.
You can run SonarQube on Amazon Linux 2. The setup script (sonarqube.sh) handles the full installation automatically:
#!/bin/bash
# Change to the /opt directory
cd /opt/
# Install required packages (wget, unzip, Java 11)
dnf install wget unzip java-11-amazon-corretto -y
# Download SonarQube 8.9.6
wget https://binaries.sonarsource.com/Distribution/sonarqube/sonarqube-8.9.6.50800.zip
# Extract it
unzip sonarqube-8.9.6.50800.zip
# Create a dedicated user to run SonarQube
useradd sonar
# Set ownership
chown -R sonar:sonar /opt/sonarqube-8.9.6.50800
# Set permissions
chmod -R 755 /opt/sonarqube-8.9.6.50800
# Required kernel settings (SonarQube needs these to function correctly)
echo "vm.max_map_count=262144" >> /etc/sysctl.conf
echo "fs.file-max=65536" >> /etc/sysctl.conf
sysctl -p
# Increase OS limits for the sonar user
cat >> /etc/security/limits.conf <<EOF
sonar - nofile 65536
sonar - nproc 4096
EOF
# Set Java 11 as the default for this session
alternatives -set java /usr/lib/jvm/java-11-amazon-corretto.x86_64/bin/java
# Start SonarQube as the sonar user
su - sonar -c "/opt/sonarqube-8.9.6.50800/bin/linux-x86-64/sonar.sh start"
Why a dedicated user? Running SonarQube (or any service) as root is a security risk. If the process is compromised, the attacker has root access. A dedicated sonar user limits the blast radius.
Why Java 11 specifically? SonarQube 8.9.x has specific Java version requirements. It is compatible with Java 11 but not with later versions. Always check the SonarQube documentation for your version's supported JDK range.
Running the Setup
Copy the sonarqube.sh script to your SonarQube EC2 instance and execute it:
chmod +x sonarqube.sh
sudo bash sonarqube.sh
SonarQube takes a minute or two to fully initialize. The script starts it with the sonar.sh start command. You do not need to run that again separately.
Accessing SonarQube
SonarQube runs on port 9000. Open in your browser:
http://<sonarqube-ec2-public-ip>:9000
Default credentials:
Username:
adminPassword:
admin
You will be prompted to change the password on first login.
Generating a SonarQube Authentication Token
Jenkins needs to authenticate with SonarQube to submit analysis results. SonarQube uses tokens for this rather than passwords.
To generate a token:
Log in to SonarQube.
Click Administration (or your profile icon) and select Security.
Under Tokens, enter a name (for example,
jenkins-token) and click Generate.Copy the generated token immediately - you will not be able to see it again after navigating away.
Installing Jenkins Plugins for SonarQube
Back in Jenkins:
Go to Manage Jenkins.
Click Plugins, then Available plugins.
Search for and install the following:
SonarQube Scanner - provides the scanner integration and pipeline steps.
Sonar Quality Gates - allows pipelines to fail based on SonarQube quality gate results.
Maven Integration Plugin - if not already installed.
Click Install and restart Jenkins.
Adding the SonarQube Token as a Jenkins Credential
Jenkins needs to store the SonarQube token securely:
Go to Manage Jenkins, Credentials, Global credentials, Add Credentials.
Fill in:
Kind: Secret text
Secret: Paste the SonarQube token you generated
ID:
sonar-tokenDescription: SonarQube authentication token
Click Create.
Configuring the SonarQube Server in Jenkins
Go to Manage Jenkins.
Click System.
Scroll down to find the SonarQube servers section.
Check Enable injection of SonarQube server configuration as build environment variables.
Click Add SonarQube.
Fill in:
Name:
SonarQube(this name is referenced in pipeline scripts)Server URL:
http://<sonarqube-ec2-public-ip>:9000Server authentication token: Select
sonar-token
Click Save.
Configuring the SonarQube Scanner Tool
The SonarQube Scanner is the component that actually runs code analysis. Jenkins needs to know where to find it (or download it):
Go to Manage Jenkins.
Click Tools.
Scroll to the SonarQube Scanner section.
Click Add SonarQube Scanner.
Fill in:
Name:
SonarScannerCheck Install automatically (Jenkins downloads it when needed)
Click Save.
Adding the Code Quality Stage to Your Pipeline
Now everything is wired up. Adding SonarQube analysis to your pipeline requires a specific code block that runs the scanner with the right configuration.
The key step uses withSonarQubeEnv, which injects the SonarQube server URL and credentials into the build environment automatically:
stage('Code Quality Check') {
steps {
withSonarQubeEnv('SonarQube') {
sh '''
mvn sonar:sonar \
-Dsonar.projectKey=my-java-app \
-Dsonar.sources=src \
-Dsonar.java.binaries=target
'''
}
}
}
The 'SonarQube' string inside withSonarQubeEnv must match the Name you gave the SonarQube server in the Jenkins system configuration.
The Complete CI Pipeline with SonarQube
Here is the full pipeline with all stages integrated:
pipeline {
agent any
stages {
stage('Checkout') {
steps {
git branch: 'main', url: 'https://github.com/your-username/java-app.git'
}
}
stage('Compile') {
steps {
sh 'mvn compile'
}
}
stage('Test') {
steps {
sh 'mvn test'
}
}
stage('Code Quality Check') {
steps {
withSonarQubeEnv('SonarQube') {
sh '''
mvn sonar:sonar \
-Dsonar.projectKey=my-java-app \
-Dsonar.sources=src \
-Dsonar.java.binaries=target
'''
}
}
}
stage('Package') {
steps {
sh 'mvn clean package'
}
}
stage('Upload Artifact') {
steps {
echo 'Uploading WAR to S3 or Nexus...'
}
}
stage('Deploy to Tomcat') {
steps {
deploy adapters: [tomcat9(credentialsId: 'tomcat-creds',
path: '',
url: 'http://<tomcat-ip>:8080')],
contextPath: 'my-web-app',
war: '**/*.war'
}
}
}
}
Every stage in the CI/CD sequence is now present: Checkout, Compile, Test, Code Quality Check, Package, Upload Artifact, Deploy.
Reading the SonarQube Dashboard
After the pipeline runs, open the SonarQube dashboard:
http://<sonarqube-ip>:9000
Click on your project to see the analysis results. The dashboard shows:
Quality Gate status: Passed or Failed
Bugs count
Vulnerabilities count
Code Smells count with severity levels
Duplications percentage
Lines of code analyzed
One common finding on fresh projects is commented-out code. SonarQube flags this as a code smell:
"Remove this commented-out code."
This is a best-practice enforcement. Commented code should not live in the main branch. If you need to refer to old code, version control (Git) stores the entire history. You can always check out an older commit.
The DevOps engineer's job here is to set up SonarQube, run the scan, and give the dashboard access to developers. If the quality gate fails, it is the developer's responsibility to fix the issues and push a new commit. The pipeline runs again, scans again, and the cycle continues until the quality gate passes.
OWASP Dependency Check: An Additional Security Layer
SonarQube is not the only security tool you can add to a Jenkins pipeline. OWASP Dependency Check is another commonly used tool that falls under DevSecOps. It scans your project's declared dependencies (the libraries listed in pom.xml) and checks them against a public database of known vulnerabilities (CVEs - Common Vulnerabilities and Exposures).
This is different from SonarQube:
SonarQube checks your code for quality and security issues.
OWASP Dependency Check checks your dependencies (third-party libraries) for known vulnerabilities.
Both checks are necessary for a comprehensive security posture.
To add OWASP Dependency Check:
Install the OWASP Dependency-Check Plugin from Jenkins plugins.
In the pipeline, add a stage:
stage('OWASP Dependency Check') {
steps {
dependencyCheck additionalArguments: '-scan ./ -format HTML', odcInstallation: 'OWASP-DC'
dependencyCheckPublisher pattern: '**/dependency-check-report.xml'
}
}
Note: The first run downloads the vulnerability database, which takes significant time (15+ minutes). Subsequent runs are much faster since the database is cached locally. For learning purposes, you can start the run and then abort it if you just want to see that it works.
Summary
SonarQube completes the testing phase of your CI/CD pipeline. Here is a recap of everything covered:
SonarQube analyzes code for bugs, vulnerabilities, code smells, and duplications.
It requires a
t2.mediuminstance (minimum) and a dedicated user account.Jenkins integrates with SonarQube via the SonarQube Scanner plugin.
Authentication uses a SonarQube-generated token stored in Jenkins credentials.
Server and scanner configuration lives in Manage Jenkins, System and Tools respectively.
The
withSonarQubeEnvpipeline step injects credentials and runs the analysis.The quality gate result (Passed/Failed) tells you whether the code meets your team's standards.
OWASP Dependency Check is a complementary tool that scans third-party library vulnerabilities.
With SonarQube integrated, your pipeline now covers all five stages: code (GitHub), build (Maven), test (SonarQube), artifact (S3/Nexus), deploy (Tomcat). That is a complete, production-grade Jenkins CI/CD pipeline.
The Complete Jenkins CI/CD Pipeline: Putting It All Together
Here is what the final combined pipeline looks like when every integration is in place:
pipeline {
agent any
stages {
stage('Checkout') {
steps {
git branch: 'main', url: 'https://github.com/your-org/java-app.git'
}
}
stage('Compile') {
steps {
sh 'mvn compile'
}
}
stage('Test') {
steps {
sh 'mvn test'
}
}
stage('Code Quality - SonarQube') {
steps {
withSonarQubeEnv('SonarQube') {
sh 'mvn sonar:sonar -Dsonar.projectKey=java-app'
}
}
}
stage('Package') {
steps {
sh 'mvn clean package'
}
}
stage('Upload to Nexus') {
steps {
nexusArtifactUploader(
nexusVersion: 'nexus3',
protocol: 'http',
nexusUrl: '<nexus-ip>:8081',
groupId: 'com.example',
version: '1.0-SNAPSHOT',
repository: 'hotstar-repo',
credentialsId: 'nexus-creds',
artifacts: [[
artifactId: 'myapp',
classifier: '',
file: 'target/myapp.war',
type: 'war'
]]
)
}
}
stage('Deploy to Tomcat') {
steps {
deploy adapters: [tomcat9(credentialsId: 'tomcat-creds',
path: '',
url: 'http://<tomcat-ip>:8080')],
contextPath: 'my-web-app',
war: '**/*.war'
}
}
}
}
Every commit to the repository triggers this pipeline. Code is checked out, compiled, tested, quality-checked, packaged into a WAR, stored in Nexus, and deployed to Tomcat - all automatically, all without a human doing anything manually after the initial setup.
That is DevOps.






