JaCoCo Code Coverage in Java

2 minute read

Code coverage measures what percentage of your code is executed by tests. JaCoCo is the standard coverage tool for Java — it integrates with Maven, Gradle, and all major CI/CD platforms.


Maven Plugin Setup

<build>
    <plugins>
        <plugin>
            <groupId>org.jacoco</groupId>
            <artifactId>jacoco-maven-plugin</artifactId>
            <version>0.8.12</version>
            <executions>
                <!-- Prepare agent before tests -->
                <execution>
                    <goals>
                        <goal>prepare-agent</goal>
                    </goals>
                </execution>
                <!-- Generate report after tests -->
                <execution>
                    <id>report</id>
                    <phase>test</phase>
                    <goals>
                        <goal>report</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>
mvn clean test
# Report: target/site/jacoco/index.html

Reading the Report

Open target/site/jacoco/index.html in a browser:

Package                  Line Coverage   Branch Coverage
────────────────────────────────────────────────────────
com.myapp.service        87%             72%
com.myapp.repository     45%             33%
com.myapp.model          95%             90%
────────────────────────────────────────────────────────
Total                    76%             65%

Coverage Types

Metric Measures
Line % of code lines executed
Branch % of if/else/switch branches taken
Instruction % of bytecode instructions executed
Method % of methods called at least once
Class % of classes with at least one method executed
Complexity Cyclomatic complexity covered

Color Coding in Reports

🟢 Green  — Fully covered
🟡 Yellow — Partially covered (some branches missed)
🔴 Red    — Not covered at all

Example in HTML report:

public String getStatus(int code) {
    if (code == 200) {          // 🟡 Yellow — only true branch tested
        return "OK";            // 🟢 Green
    } else if (code == 404) {   // 🔴 Red — never reached
        return "Not Found";     // 🔴 Red
    }
    return "Unknown";           // 🔴 Red
}

Enforcing Minimum Coverage

<execution>
    <id>check</id>
    <goals>
        <goal>check</goal>
    </goals>
    <configuration>
        <rules>
            <rule>
                <element>BUNDLE</element>
                <limits>
                    <limit>
                        <counter>LINE</counter>
                        <value>COVEREDRATIO</value>
                        <minimum>0.80</minimum>  <!-- 80% line coverage -->
                    </limit>
                    <limit>
                        <counter>BRANCH</counter>
                        <value>COVEREDRATIO</value>
                        <minimum>0.70</minimum>  <!-- 70% branch coverage -->
                    </limit>
                </limits>
            </rule>
        </rules>
    </configuration>
</execution>
mvn verify
# BUILD FAILURE if coverage below thresholds

Excluding Classes from Coverage

<configuration>
    <excludes>
        <exclude>com/myapp/model/*</exclude>            <!-- POJOs -->
        <exclude>com/myapp/config/*</exclude>            <!-- Config classes -->
        <exclude>com/myapp/Application.class</exclude>   <!-- Main class -->
        <exclude>**/*DTO.*</exclude>                      <!-- DTOs -->
    </excludes>
</configuration>

Generating XML Reports (for CI/CD)

<execution>
    <id>report</id>
    <phase>test</phase>
    <goals>
        <goal>report</goal>
    </goals>
    <configuration>
        <formats>
            <format>HTML</format>
            <format>XML</format>
        </formats>
    </configuration>
</execution>

XML report at target/site/jacoco/jacoco.xml — used by SonarQube, GitHub Actions, etc.


CI/CD Integration

GitHub Actions

- name: Run tests with coverage
  run: mvn clean verify

- name: Upload coverage to Codecov
  uses: codecov/codecov-action@v4
  with:
    files: target/site/jacoco/jacoco.xml
    token: ${{ secrets.CODECOV_TOKEN }}

SonarQube

mvn clean verify sonar:sonar \
  -Dsonar.coverage.jacoco.xmlReportPaths=target/site/jacoco/jacoco.xml

Coverage Guidelines

Coverage Level Assessment
90%+ Excellent — well-tested codebase
80-90% Good — aim for this in most projects
60-80% Acceptable — common in legacy code
Below 60% Risky — too many untested paths

What NOT to Obsess Over

⚠️ 100% coverage ≠ bug-free code

High coverage with bad assertions:
  @Test
  void test() {
      service.processOrder(order);  // no assertions!
  }
  // Coverage: 100%. Bugs caught: 0.

Coverage vs Quality

Scenario Coverage Quality
Tests with no assertions High Low
Tests covering happy paths only Medium Low
Tests covering edge cases & errors Medium-High High
TDD-driven tests High High

Focus on: edge cases, error paths, boundary conditions — not the coverage number itself.


Key Takeaways

  1. JaCoCo = standard Java coverage tool. Add Maven plugin, run mvn test.
  2. Line coverage = lines executed. Branch coverage = if/else paths taken.
  3. Aim for 80%+ line coverage — enforce with jacoco:check.
  4. Exclude POJOs, configs, DTOs — focus coverage on business logic.
  5. Coverage ≠ quality — meaningful assertions matter more than the number.

What’s Next?

Next: Testcontainers — spin up real databases and services in Docker for integration tests.

Updated: