JaCoCo is the code coverage library for the JVM, developed as part of the EclEmma project. It measures which bytecode was executed while a program ran, and it does so by instrumenting classes on the fly: a Java agent registers a class-file transformer, and as each class is loaded the ASM library inserts probes into it in memory. Nothing on disk is modified, no separate instrumentation build step exists, and the application under measurement is the same artefact the tests would have run against anyway. That single design decision is what separates JaCoCo from the older generation of Java coverage tools.
JaCoCo counts more than lines. It maintains five counters — instructions, branches, lines, methods and classes — plus cyclomatic complexity, and it derives them from bytecode rather than from source, which is why line coverage requires classes compiled with debug information and why a single source line can be partially covered. Execution data is written to a binary `.exec` file, keyed by a class id: a CRC64 hash of the class file itself. Reporting joins that execution data back to the class files and the sources, and if the classes have been recompiled since the run, JaCoCo refuses to guess and reports a class id mismatch instead of silently producing a wrong number.
Around that core sit the integrations most teams actually touch. The Maven plugin binds `prepare-agent`, `report`, `report-aggregate` and `check` into the build lifecycle; the Gradle plugin does the same through the JacocoReport and JacocoCoverageVerification tasks; Ant tasks and a command line interface cover everything else, and a Java API exists for tools that embed it. The agent can also expose execution data over TCP so a long-running server can be dumped without stopping it, and an offline instrumentation mode exists for the runtimes where a Java agent is not an option at all.
Why this skill matters now
Coverage stopped being a report someone reads and became a gate that blocks a merge. Pull request decoration in SonarQube, GitLab and GitHub all consume a coverage number per change, and on the JVM that number is almost always produced by JaCoCo — it is the de facto standard, the format SonarQube expects, and the one that build tools ship support for out of the box.
That makes it worth understanding properly rather than copying from a sample POM. The failure modes are specific and common: a plugin configured so the agent argument never reaches the test JVM, forked or containerised test executions that write execution data somewhere the report never looks, multi-module projects where each module reports on itself and nobody aggregates, integration tests whose coverage is discarded because only the surefire run was measured, and generated code that drags a perfectly healthy number down until the right filters are applied.
The wider reason is the coverage gate itself. Setting a blanket eighty percent target on a legacy codebase reliably produces tests written to touch code rather than to assert anything. Configuring `check` rules on new code, on the counters that matter, at the scope where the team can act, is a different and far more useful skill — and it is one that requires knowing what JaCoCo is measuring rather than just what number it printed.