# Diagnose SonarQube showing 0% or missing coverage: report import failures
Use this when SonarQube shows 0% coverage, "No coverage information", or coverage
that does not match what your coverage tool reports locally. Classify from the
scanner log first — the fix depends on which sensor line you see.
The one fact that organizes everything: **SonarQube never computes coverage; it
only imports reports.** If the UI shows nothing, either no report was imported or
the imported report's file paths did not match the indexed sources. Your coverage
tool is not broken until proven otherwise — check the import first.
## 1. Check what the coverage sensor actually did
Re-run the scanner (verbose is not required) and find the coverage sensor lines:
- **No coverage sensor lines at all, only `Sensor Zero Coverage Sensor`** — no
report was found or no report property is configured. The Zero Coverage Sensor
marks files it cannot match to any report as 0%. Go to step 2.
- **`No coverage report can be found with sonar.coverage.jacoco.xmlReportPaths='...'
Using default locations: target/site/jacoco/jacoco.xml,target/site/jacoco-it/jacoco.xml,build/reports/jacoco/test/jacocoTestReport.xml`**
— the configured path did not match anything on disk. Go to step 3.
- **`WARN: Could not resolve 47 file paths in [.../lcov.info]` / `First unresolved
path: ...`** (JavaScript/TypeScript) — the report was found, but the `SF:` paths
inside it do not resolve to analyzed files. Go to step 4.
- **`Analysing [...]` then `Importing 1 report(s)` but the UI still shows 0%** —
the import ran, but the report's files are not indexed as sources (excluded via
`sonar.exclusions`, outside `sonar.sources`, or a stale report from a renamed
module). Go to step 5.
## 2. Java / JaCoCo: it must be XML, and it must exist before the scan
- SonarQube imports the JaCoCo **XML** report only. The old binary format
(`.exec`) and the deprecated `sonar.jacoco.reportPaths` property are no longer
honored. Configure the JaCoCo Maven plugin with a `prepare-agent` execution and
a `report` execution bound to the `test` phase, then point the scanner at the
XML with `sonar.coverage.jacoco.xmlReportPaths` (or rely on the defaults in the
log message above).
- The coverage report must be generated **in the same pipeline run, before the
scanner step**. `mvn clean install sonar:sonar` works because `verify`-phase
tests and the `report` goal run before `sonar:sonar`; a pipeline that runs
tests and analysis as independent parallel jobs with no shared workspace has
nothing to import.
- Use paths relative to the project base directory, not absolute ones — absolute
paths break as soon as the workspace moves (Docker builds are the classic
case). Ant-style wildcards such as `**/jacoco.xml` are supported for
multi-module layouts.
- For Gradle multi-module projects, aggregate first: generate one full report
task that merges every submodule's execution data, and point every submodule's
`sonar.coverage.jacoco.xmlReportPaths` at that single full report. Pointing at
per-module reports undercounts classes covered from other modules.
## 3. JavaScript / TypeScript: `SF:` paths must anchor to the project base dir
The JS/TS coverage sensor (`sonar.javascript.lcov.reportPaths`, default
`coverage/lcov.info`) resolves each `SF:` line against the project base directory.
Anything that shifts the anchor breaks every match and yields 0% on new code:
- Tests run from a subdirectory so `SF:` lines read `../../src/...` — rewrite them
to project-root-relative paths in CI before the scan step.
- Absolute `SF:` paths baked in a Docker image or on a developer's machine that do
not exist on the CI agent.
- The old `sonar.typescript.lcov.reportPaths` property is retired; both languages
go through `sonar.javascript.lcov.reportPaths` now.
## 4. Other stacks: the report property per language
- Python: `sonar.python.coverage.reportPaths` (default `coverage.xml` from
`coverage.py` — generate with `coverage xml`, not just `coverage report`).
- .NET: `sonar.cs.opencover.reportsPaths` (Coverlet with the `opencover` format)
or `sonar.cs.vscoveragexml.reportsPaths`; Coverlet's default `cobertura` format
needs the matching Cobertura property.
- Cobertura XML (any language): `sonar.coverage.cobertura.xmlReportPaths`.
## 5. If the import ran but the UI disagrees
- Files listed in the report must be indexed as **sources**, not tests and not
excluded. `sonar.coverage.exclusions` is the right place for generated or
vendored code; if the report references a file that `sonar.exclusions` removes,
its coverage silently vanishes.
- Lombok-annotated Java classes show artificially low condition coverage because
JaCoCo instruments the generated methods. The fix is report-side, in a
`lombok.config` at the repo root with `lombok.addLombokGeneratedAnnotation = true`,
so JaCoCo omits generated code from the XML SonarQube imports.
- A class with only generated code may show **no coverage information** rather
than 0% — that is the report containing the file with zero coverable lines,
not an import failure.
- New-code coverage is computed from changed lines (blame data). On a shallow
clone the scanner warns `Shallow clone detected, no blame information will be
provided`; overall coverage can look fine while new-code coverage is empty.
## 6. Checklist for the failing import
1. Confirm the report file exists on disk in the scan workspace, generated by the
test step in the same run.
2. Confirm the format: JaCoCo XML (never `.exec`), lcov with `SF:` paths,
`coverage.xml` for Python, OpenCover/Cobertura for .NET.
3. Match the property to the language and make the path relative to the project
base dir (wildcards allowed).
4. Read the sensor lines: "No coverage report can be found" = path problem;
"Could not resolve N file paths" = anchor problem; silent + Zero Coverage
Sensor = nothing found at all.
5. Confirm the report's files are indexed sources (not excluded, not test-only).
6. For new-code coverage specifically, confirm the clone has full history so blame
data exists.