Your team needs to analyze code in a monorepo with 30 services using different languages. How do you configure SonarQube for multi-language monorepo analysis with proper project isolation and reporting?
Quick Answer
Configure SonarQube with either a single project (multi-language support) with module-level reporting, or individual projects per service connected via portfolio/application for aggregate views. Use `sonar.modules` for sub-project definition, language-specific properties per module, and PR analysis scoped to changed modules only.
Detailed Answer
Monorepo Strategies
Two approaches: (1) Single SonarQube project with modules - simpler setup, one analysis run, but all code analyzed together (slower, mixed metrics). (2) Multiple SonarQube projects (one per service) - better isolation, independent Quality Gates, per-service ownership, but requires multiple scanner invocations. For 30 services, approach #2 is recommended because teams own their quality independently.
Multi-Project Setup
Create a SonarQube project per service (orders-api, payments-api, user-service). In CI, determine which services changed (from Git diff), run analysis only on affected services. Use SonarQube Portfolios (Enterprise Edition) to create aggregate views across all services for management reporting. Each service has its own Quality Gate, Quality Profile, and team ownership.
Multi-Language Configuration
SonarQube automatically detects languages by file extension. Configure language-specific properties per module: Java needs compiled bytecode paths, TypeScript needs tsconfig location, Python needs virtual environment. Set sonar.sources and sonar.tests per module. Use sonar.language only if you want to RESTRICT analysis to one language (rarely needed).
CI Pipeline Architecture
Use a matrix/parallel strategy in CI to analyze multiple services concurrently. Each parallel job runs its own scanner instance targeting a different SonarQube project. Implement a 'changed service detection' script that maps file paths to services and triggers only relevant analyses. This reduces a 30-service monorepo analysis from 45 minutes (sequential) to 10 minutes (parallel, only affected services).
Code Example
# Monorepo structure:
# /services/order-api/ (Java + Spring Boot)
# /services/payment-api/ (Java + Spring Boot)
# /services/user-service/ (TypeScript + Node.js)
# /services/notification/ (Python + FastAPI)
# /libs/common-utils/ (Java library)
# /infrastructure/ (Terraform - excluded)
# GitHub Actions: parallel analysis of affected services
name: SonarQube Monorepo
on: [push, pull_request]
jobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
services: ${{ steps.changes.outputs.services }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- id: changes
run: |
CHANGED=$(git diff --name-only origin/main...HEAD | \
grep '^services/' | cut -d'/' -f2 | sort -u | \
jq -R -s -c 'split("\n") | map(select(length > 0))')
echo "services=$CHANGED" >> $GITHUB_OUTPUT
analyze:
needs: detect-changes
if: needs.detect-changes.outputs.services != '[]'
runs-on: ubuntu-latest
strategy:
matrix:
service: ${{ fromJson(needs.detect-changes.outputs.services) }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Analyze ${{ matrix.service }}
run: |
cd services/${{ matrix.service }}
sonar-scanner \
-Dsonar.projectKey=${{ matrix.service }} \
-Dsonar.projectBaseDir=. \
-Dsonar.sources=src \
-Dsonar.tests=tests
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
# Per-service sonar-project.properties
# services/order-api/sonar-project.properties
sonar.projectKey=order-api
sonar.projectName=Order API
sonar.sources=src/main/java
sonar.tests=src/test/java
sonar.java.binaries=target/classes
sonar.coverage.jacoco.xmlReportPaths=target/site/jacoco/jacoco.xml
sonar.qualitygate.wait=true
# services/user-service/sonar-project.properties
sonar.projectKey=user-service
sonar.projectName=User Service
sonar.sources=src
sonar.tests=tests
sonar.typescript.tsconfigPaths=tsconfig.json
sonar.javascript.lcov.reportPaths=coverage/lcov.info
# services/notification/sonar-project.properties
sonar.projectKey=notification-service
sonar.projectName=Notification Service
sonar.sources=app
sonar.tests=tests
sonar.python.coverage.reportPaths=coverage.xml
sonar.python.version=3.11
# Portfolio configuration (via API) for aggregate reporting
curl -u admin:$TOKEN -X POST \
"https://sonar.company.com/api/views/create" \
-d "key=all-services&name=All+Microservices&qualifier=VW"
# Add projects to portfolio
for svc in order-api payment-api user-service notification-service; do
curl -u admin:$TOKEN -X POST \
"https://sonar.company.com/api/views/add_project" \
-d "key=all-services&project=$svc"
doneInterview Tip
The key insight is NOT to analyze everything every time. Show the changed-service-detection pattern that maps Git diffs to affected services and runs parallel analysis. Mention Portfolios for aggregate views. The single-project-with-modules approach fails at scale because one service's failure blocks all.