What is JaCoCo, and how does it measure code coverage on the JVM?
answer
- Bytecode, not source — works for any JVM language
- On-the-fly agent rewrites classes at load time, inserts probes
- jacoco.exec = binary execution data; needs classes + sources to report
- Probes = boolean flags flipped when code runs
- Executed != asserted (coverage is necessary, not sufficient)
basics
~20 sJaCoCo is a Java tool that measures how much of your code your tests actually run. While the tests execute, it watches the compiled code and records which lines and branches were hit, then produces a report.
solid answer
~40 sJaCoCo (Java Code Coverage) is the de-facto coverage library for the JVM. It works on compiled bytecode, not source, using on-the-fly instrumentation: a Java agent attached to the JVM rewrites each class as it is loaded, inserting tiny probes that flip a flag when a piece of code executes. During a test run these probes record what was reached; afterwards JaCoCo writes the execution data (a jacoco.exec file) and, combined with the class files and sources, renders HTML/XML/CSV reports. Because it instruments bytecode it needs no source changes and works with any JVM language (Java, Kotlin, Groovy). It reports several metrics — lines, branches, instructions, methods, classes, and cyclomatic complexity. The result tells you which code your tests exercised, not whether the tests assert anything meaningful — coverage is necessary, not sufficient.
go deeper
Knows JaCoCo measures what percentage of code the tests run, and that the build produces an HTML report you can open.
Can explain on-the-fly instrumentation via a Java agent and probes, the jacoco.exec file, and that report generation needs classes + sources.
Distinguishes on-the-fly vs offline instrumentation and when each is needed; articulates that coverage measures execution not assertion quality, and uses it as a floor, not a goal.
Frames coverage in a broader quality strategy (mutation testing, risk-based targets), understands agent interaction with other bytecode tooling/class loaders, and sets sane org-wide policies that avoid gaming.
## What "code coverage" means **Code coverage** is a measurement of how much of your program's code was *executed* while your tests ran. If a line of code never runs during any test, no test could possibly have caught a bug in it. Coverage is expressed as a percentage: *covered units / total units*. **JaCoCo** (pronounced "jah-co-co", short for **Ja**va **Co**de **Co**verage) is the most widely used coverage library on the **JVM** (Java Virtual Machine — the runtime that executes Java, Kotlin, Scala, Groovy, etc.). ## Source vs. bytecode When you compile Java, the `.java` source becomes `.class` files containing **bytecode** — the low-level instruction format the JVM actually runs. JaCoCo measures coverage on this **bytecode**, not on your source text. This is why it works for *any* JVM language and needs no changes to your source files. ## Instrumentation and probes **Instrumentation** means inserting extra bookkeeping code into a program so its behavior can be observed. JaCoCo inserts **probes**: tiny boolean flags that get set to `true` the moment a stretch of code executes. After the run, JaCoCo knows a piece of code was reached if its probe is `true`. JaCoCo offers two instrumentation modes: 1. **On-the-fly (default):** a **Java agent** (a special library passed to the JVM with `-javaagent:jacocoagent.jar`) hooks into class loading. As each class is loaded, the agent rewrites its bytecode *in memory* to add probes. The `.class` files on disk are never modified. This is transparent and needs no extra build step. 2. **Offline:** JaCoCo rewrites the `.class` files *before* running, producing instrumented copies on disk. Used when an agent can't be attached (some Android setups, certain custom class loaders, frameworks that themselves rewrite bytecode). ## The execution-data file As tests run, probes accumulate in memory. When the JVM finishes (or on demand), JaCoCo dumps them to a binary **execution-data file**, conventionally `jacoco.exec` (or `*.exec`). This file alone is not human-readable — it just records which probes fired. ## Producing a report To turn execution data into a report, JaCoCo combines three inputs: - the **execution data** (`*.exec`) — what ran, - the **compiled classes** — to map probes back to methods and lines, - the **source files** — to show you the actual lines, colored green (covered), yellow (partially covered), or red (missed). The report can be **HTML** (for humans), **XML** (for tools/CI like SonarQube), or **CSV**. ## What it measures (the metrics) JaCoCo computes several counters: **instructions** (single bytecode instructions, its finest unit), **branches** (the true/false arms of `if`/`switch`/`?:`), **lines**, **methods**, **classes**, and **cyclomatic complexity** (a count of independent paths). A later question covers each in detail. ## The crucial caveat Coverage tells you code was **executed**, not that it was **tested**. A test that calls a method but asserts nothing still marks it covered. So high coverage is *necessary but not sufficient* for confidence — it bounds the *minimum* of what your tests touch, not the *quality* of their assertions.
- Why can JaCoCo measure coverage for Kotlin and Groovy without any extra configuration?Because it instruments JVM bytecode, not Java source. Any language that compiles to .class files (Kotlin, Groovy, Scala) is measured the same way; JaCoCo never looks at the original source to insert probes.
- What three artifacts does JaCoCo need to render an HTML report?The execution-data file (*.exec, what ran), the compiled class files (to map probes to methods/lines), and the source files (to display and color the actual lines).
saying these in an interview costs you the question
- Saying JaCoCo instruments the source code — it instruments bytecode.
- Claiming high coverage proves the code is well tested — it only proves code was executed, not asserted.
- Thinking jacoco.exec is the report — it is opaque binary execution data; a separate report step is required.
- Believing it only works for Java — it works for any JVM language.