Part 3: Scanning Java Code with SonarQube (SAST)
📘 This post is part of the DevSecOps for Java series:
🔎 Why Scan Your Code?
In Part 2, we scanned our dependencies with Snyk (SCA).But what about the actual Java code we write?
That’s where SAST comes in — Static Application Security Testing. It’s a fancy term for “scan your source code for security issues and code quality problems before it’s compiled or deployed.”
We’ll use SonarQube to do exactly that.
🧠 What Does SonarQube Do?
SonarQube scans your code and detects:
-
Code smells (bad practices, redundancy, maintainability)
-
Bugs (null pointers, logic errors, bad conditions)
-
Security vulnerabilities (like SQL injection, unsafe APIs)
-
Test coverage gaps (if you integrate with testing tools)
It’s like a security + quality assistant baked into your pipeline.
🔧 Running SonarQube in Azure DevOps
There are two common ways to use SonarQube:
-
Self-hosted SonarQube (running on a server you manage)
-
SonarCloud, the hosted version (easier to get started)
We’ll walk through the self-hosted option here. If you’re using SonarCloud, the steps are nearly the same.
✅ Step 1: Install SonarQube and Get the Token
-
Run SonarQube locally (Docker or zip installer)
-
Create a project and generate a project token
-
Store that token in Azure DevOps as a secret variable: SONAR_TOKEN
✅ Step 2: Add SonarQube Service Connection (if needed)
In Azure DevOps → Project Settings → Service connections→ Add a SonarQube connection with your server URL→ Give it a name like SonarQubeService
✅ Step 3: Add SonarQube Stage to Your YAML
Here’s how you can configure your Azure DevOps pipeline to include SonarQube for static code analysis:
- stage: SAST
displayName: 'SAST with SonarQube'
dependsOn: SCA
jobs:
- job: sonarScan
displayName: 'Scan Java Code with SonarQube'
pool:
vmImage: 'ubuntu-latest'
steps:
- checkout: self
- task: SonarQubePrepare@5
inputs:
SonarQube: 'SonarQubeService' # Name of your service connection
scannerMode: 'CLI'
configMode: 'manual'
cliProjectKey: 'devsecops-java'
cliProjectName: 'DevSecOps Java'
extraProperties: |
sonar.java.binaries=target
sonar.sources=src
sonar.sourceEncoding=UTF-8
- script: mvn clean verify
displayName: 'Build Java Project'
- task: SonarQubeAnalyze@5
displayName: 'Run SonarQube Scan'
- task: SonarQubePublish@5
inputs:
pollingTimeoutSec: '300'
displayName: 'Publish SonarQube Results'
🧠 How It Works
-
Prepare: Configures the scanner with project info
-
Build: You run mvn verify, which compiles the code and runs tests
-
Analyze: SonarQube scans the compiled bytecode and source code
-
Publish: Sends results to your SonarQube dashboard
❌ What Can It Catch?
Examples:
-
SQL injection (Statement.execute(“SELECT * FROM " + userInput))
-
Hardcoded credentials
-
Repeated code blocks
-
Missed null checks
-
Unused variables or methods
And much more.
✅ Pro Tips
-
Keep SonarQube running and connected during the scan
-
You can fail the pipeline if code quality is below a threshold (like < 80%)
-
Use Quality Gates to enforce rules
🔄 Example Output
After the scan, you’ll get:
-
✅ A code quality score
-
🐛 A list of bugs
-
⚠️ Security hotspots
-
🧼 Suggestions for improvement
-
💯 Coverage stats (if tests are run with coverage reports)
⏭️ What’s Next?
We’ve now scanned:
-
✅ Dependencies (SCA with Snyk)
-
✅ Code (SAST with SonarQube)
Next up?
We shift our attention to the infrastructure layer.In Part 4, we’ll scan our Terraform files using Checkov — and make sure we’re not deploying anything with public IPs, overly open firewalls, or hardcoded secrets.
👉 Continue to Part 4 – Terraform Security Scanning with Checkov