ScreenshotNeo

BlogGuides

Java Code Coverage Tools: How to Measure Test Coverage

Measure Java test coverage with JaCoCo or IntelliJ IDEA. Configure Maven or Gradle, generate reports, understand coverage metrics, and use results to guide better tests.

By the ScreenshotNeo team4 October 20269 min read

For repeatable Java coverage reports in Maven or Gradle, JaCoCo is the practical build-integrated choice. For interactive local inspection, IntelliJ IDEA can show coverage by class, method, line, and branch. Run tests with coverage instrumentation enabled, generate a report, then use uncovered lines and branches to find important behavior that lacks tests. Coverage measures execution; it does not prove that tests would detect a defect.

1. Choose a Java coverage tool

Need Option What to know
Repeatable build reports and CI checks with Gradle Gradle JaCoCo plugin Integrates with Java test tasks and supports verification rules. Run tests before jacocoTestReport; the report task does not run them automatically. Gradle JaCoCo Plugin documentation.
Maven test and report workflow JaCoCo Maven plugin Attaches the JaCoCo Java agent and creates reports. The test process must be forked for the documented Surefire/Failsafe setup. JaCoCo Maven documentation.
Local interactive inspection IntelliJ IDEA coverage runner Shows coverage in the IDE. Branch coverage is available with JaCoCo or with the IDEA runner when branch coverage is enabled. IntelliJ IDEA coverage documentation.
One combined Gradle report for multiple projects Gradle JaCoCo report aggregation Aggregates coverage reports across Gradle projects into an HTML report. Gradle report aggregation documentation.

Choose based on build integration, the metrics you need, local inspection, multi-module reporting, and how reliably the tool instruments your test process. Check the documentation for the exact Gradle, Maven, JaCoCo, and IDE versions in your project.

2. Measure coverage with Gradle

Apply the JaCoCo plugin to the project that runs Java tests. This Groovy DSL example adds report generation and an optional instruction coverage rule:

plugins {
    id 'java'
    id 'jacoco'
}

jacoco {
    // Optional: set the JaCoCo tool version explicitly for your project.
    toolVersion = '0.8.13'
}

tasks.named('test') {
    useJUnitPlatform()
}

tasks.named('jacocoTestReport') {
    reports {
        xml.required = true
        html.required = true
        csv.required = false
    }
}

tasks.named('jacocoTestCoverageVerification') {
    violationRules {
        rule {
            element = 'BUNDLE'
            limit {
                counter = 'INSTRUCTION'
                value = 'COVEREDRATIO'
                minimum = 0.70
            }
        }
    }
}

Use a JaCoCo version supported by your project; the version above is an example, not a compatibility recommendation. The verification rule is optional and sets a project-chosen minimum. It can make the build fail when the configured rule is violated. Decide the scope and threshold for your codebase rather than treating 70% or any other number as a universal target.

Run tests before generating the report. To generate the report and then check the configured threshold, run:

./gradlew clean test jacocoTestReport jacocoTestCoverageVerification

Without the verification task, use:

./gradlew clean test jacocoTestReport

The HTML report is normally at build/reports/jacoco/test/html/index.html; XML is useful when another CI or reporting tool consumes the result. For a multi-project build, configure aggregation when you need a combined view across subprojects; see the aggregation plugin guide.

3. Measure coverage with Maven

JaCoCo’s Maven plugin can prepare an agent for tests, then generate a report in the Maven reporting lifecycle. This example configures the plugin and runs tests before creating the report:

<build>
  <plugins>
    <plugin>
      <groupId>org.jacoco</groupId>
      <artifactId>jacoco-maven-plugin</artifactId>
      <version>0.8.13</version>
      <executions>
        <execution>
          <goals>
            <goal>prepare-agent</goal>
          </goals>
        </execution>
        <execution>
          <id>report>
          <phase>verify</phase>
          <goals>
            <goal>report</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

Correct the execution ID element in that configuration to the standard Maven form shown here:

<id>report</id>

Then run:

mvn clean verify

The HTML report is typically under target/site/jacoco/. If you prefer to keep the report goal separate, run tests first and then invoke the report goal:

mvn clean test
mvn jacoco:report

In the documented Surefire/Failsafe configuration, tests need a forked JVM for the JaCoCo agent. Settings such as forkCount=0 or forkMode=never prevent collection. Source line mapping also requires debug information. See JaCoCo’s Maven setup details when adapting this to a multi-module build or custom test lifecycle.

4. Inspect coverage in IntelliJ IDEA

  1. Open the test or test suite you want to inspect.
  2. Use the IDE’s coverage action to run it with coverage enabled, choosing the JaCoCo or IDEA runner as appropriate.
  3. Review highlighted source lines and the coverage results for classes and methods.
  4. Enable branch coverage if you need to see whether conditional outcomes were exercised; availability depends on runner and settings.
  5. After changing tests or production code, rerun the coverage configuration so the view reflects the current run.

IDE coverage is useful for short feedback loops. Keep a build-generated report as the repeatable CI record for team-wide checks.

5. Understand what the percentages count

JaCoCo instruments Java bytecode. Its counters answer related but different questions:

Metric What it tells you Important limit
Instruction coverage How many bytecode instructions ran. It is not source-line coverage and can change with compiler output.
Branch coverage How many outcomes of branches associated with if and switch ran. Exception handling is not counted as branch coverage by JaCoCo’s counter.
Line coverage Whether instructions mapped to source lines ran; a line is covered if at least one instruction assigned to it executes. Source line information must be present in class files.
Method and class coverage Whether methods and classes were entered. These broader measures can hide untested behavior within a method.
Complexity counters Summaries of missed and covered complexity. They are indicators to investigate, not direct measures of test quality.

Line and branch coverage are not interchangeable. A test can execute the line containing an if while exercising only its true outcome. JaCoCo describes its counters in the counter documentation.

A high percentage means the selected metric observed more of the code during the selected test run. It does not show whether assertions are meaningful, whether tests would fail for a regression, or whether the right code was included. Interpret uncovered behavior by risk: focus on important decisions, boundaries, error handling, and state changes.

6. Choose scope and thresholds

Before adding a coverage gate, decide what the report represents:

  • Which production source directories and modules count? Exclude or handle generated code deliberately.
  • Do unit and integration tests run together, or should their reports remain separate?
  • Will the metric be instruction, line, branch, or a deliberate combination?
  • Is the threshold achievable without encouraging tests that execute code but assert little?
  • Should the gate apply to the whole bundle or a narrower scope?

JaCoCo and Gradle support configurable verification rules. A failing gate means the configured limit was missed; it does not diagnose why coverage is low. For legacy systems, consider choosing a scope that supports useful progress instead of making unrelated code changes part of every test failure.

7. Use the report to improve tests

  1. Start with missed lines and branches in code that matters to users or operations.
  2. Identify the behavior behind each gap: a boundary value, an error condition, a state transition, or a decision outcome.
  3. Add a test that checks the expected behavior with useful assertions.
  4. Rerun the relevant tests and report task, then inspect whether the new test covers the intended path.
  5. Keep the report artifact or XML in CI if the team needs a reviewable trend or downstream processing.

Do not add a test only to change a line’s color. A test that executes a line without checking an outcome may increase coverage without making regressions easier to detect.

8. Or skip the browser setup

Java coverage tools measure test execution. If your engineering workflow also needs a clean screenshot of a coverage report page for a pull request, documentation, or an agent, ScreenshotNeo is a website screenshot API and MCP server. It is separate from Java coverage instrumentation and does not replace JaCoCo or IntelliJ.

One GET request captures a report page. See the ScreenshotNeo API documentation for options.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com/report -o report.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://example.com/report"}, timeout=90)
open("report.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com/report' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('report.webp', res);
  • Cookie and consent banners are accepted and removed before capture; 60+ known consent platforms, newsletter popups, and chat widgets can be removed, and each step can be turned off.
  • Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing; response headers identify the page verdict and billing status.
  • An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.
  • It includes full-page and element capture, wait conditions, custom headers, cookies, caching, and other options. 1,000 screenshots per month are free with no card; paid plans start at $5 for 3,000.

Sign up for 1,000 free screenshots a month, with no card required.

9. Performance, reliability, and cost

Coverage adds instrumentation and report generation to test work. Keep CI runs focused on the test tasks and modules that answer the question at hand; use aggregation when a combined multi-project report is needed. No universal runtime cost or performance percentage applies across projects, so compare your own ordinary test run with the instrumented run if the extra time matters.

For reliable data, confirm that tests actually ran under the configured agent, that the execution-data file is produced, and that report generation happens after those tests. In Maven, check the test fork configuration; in Gradle, do not assume jacocoTestReport runs tests first. Keep tool versions and report tasks explicit in the build so local and CI runs use the same setup. JaCoCo itself is a build tool dependency; the cited documentation provides no universal monetary cost estimate.

10. Troubleshooting

Symptom Likely cause Fix
Report is missing or shows no execution data Tests did not run before report generation, or the coverage agent was not active. Run the test task with coverage enabled, then generate the report. In Gradle, invoke test jacocoTestReport in that order.
Maven report has no coverage The test JVM was not forked, so the agent could not collect data in the documented setup. Remove settings such as forkCount=0 or forkMode=never for the relevant Surefire/Failsafe execution, then rerun tests.
Lines are not mapped or source highlighting is unavailable Class files lack debug line information, or the report is not associated with matching sources. Compile with debug information and generate the report from the matching build outputs.
Line coverage looks high but a decision is untested Line execution does not imply both conditional outcomes ran. Inspect branch coverage and add tests for meaningful decision outcomes.
CI fails after adding a coverage rule The measured counter or scope is below the configured minimum. Inspect missed code and verify the rule targets intended production code; adjust tests or the project-specific threshold deliberately.
Aggregate report omits a module The project is not participating in the aggregation/report setup, or its tests did not produce data. Check the Gradle aggregation configuration and ensure each included project’s test execution is part of the workflow.

11. FAQ

Does 100% coverage mean the code is correct?

No. It means the selected coverage metric was fully exercised in the measured scope. It does not establish that assertions detect incorrect behavior.

Should I use line or branch coverage?

Use the metric that fits the question. Line coverage shows source execution; branch coverage helps reveal untested outcomes of decisions. Many teams inspect both, while avoiding a threshold that rewards meaningless tests.

Can I combine unit and integration test coverage?

Yes, if your build workflow collects their execution data into the report you intend to use. Decide whether a combined number or separate reports gives the team clearer information.

Does JaCoCo count exception paths as branches?

No. JaCoCo’s branch counter covers branches associated with if and switch; exception handling is not counted as branch coverage.

Where should I start in an old project?

Generate a baseline, then add meaningful tests around high-risk behavior and changed areas. A baseline can help make progress visible without treating legacy coverage as a measure of current test quality.

Sources