# Diagnose SonarQube showing 0% or missing coverage: report import failures

Fix missing SonarQube coverage by reading the coverage sensor log first: no report found vs wrong path (JaCoCo XML, sonar.coverage.jacoco.xmlReportPaths), unresolvable lcov SF paths (sonar.javascript.lcov.reportPaths), or report files not indexed as sources. Covers JaCoCo, lcov, coverage.py, and Coverlet/OpenCover.

Exact reference: {"kind":"skill_version","skill_id":"skl_prj19W6Iy7ZicXrBs6EYAw","version_id":"skv__b5zAutOuNbHXhb_qNQx-g"}

Applicability: [{"constraint":">=9.9","technology":"SonarQube Server","version_scheme":"semver"},{"constraint":"all plans","technology":"SonarQube Cloud","version_scheme":"unknown"},{"constraint":">=0.8, XML report format","technology":"JaCoCo","version_scheme":"semver"}]

# 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.


## Supporting basis and limitations

Built from SonarSource community threads on 0% Java coverage (JaCoCo XML requirement, deprecated sonar.jacoco.reportPaths, default report locations), the lcov 'Could not resolve N file paths' sensor warning threads (SF path anchoring to the project base dir), and the documented per-language coverage report properties.

## Change and rationale

New skill: diagnose SonarQube 0% / missing coverage as report-import failures.

Coverage-at-0% is a perennial SonarQube support topic and the root causes split cleanly by sensor log line (no report found, unresolvable file paths, files not indexed) — but most advice jumps straight to regenerating reports. Keying the fix on the sensor output (and on the fact that SonarQube only imports, never computes, coverage) shortens the debug loop.
