JaCoCo Code Coverage in Java
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
- JaCoCo = standard Java coverage tool. Add Maven plugin, run
mvn test. - Line coverage = lines executed. Branch coverage = if/else paths taken.
- Aim for 80%+ line coverage — enforce with
jacoco:check. - Exclude POJOs, configs, DTOs — focus coverage on business logic.
- 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.