Generated and DTO classes are dragging down your JaCoCo numbers. How do you exclude them from the jacocoTestReport without touching coverage thresholds?
answer
- filter classDirectories
- fileTree + exclude globs
- patterns match .class paths
- setFrom replaces, from adds
- exclude generated/DTO/config, not real logic
basics
~10 sOverride the report's classDirectories to a filtered file tree, excluding paths like generated sources or config classes. Set classDirectories.setFrom(files(classDirectories.files.map { fileTree(it) { exclude("**/generated/**") } })).
solid answer
~40 s`jacocoTestReport` analyzes whatever is in its `classDirectories` input. To drop generated/boilerplate classes from the *report*, you replace that input with a filtered file tree rather than the raw class output. The idiom maps each class directory to a `fileTree` carrying an `exclude` pattern (e.g. `**/dto/**`, `**/*MapperImpl.class`, `**/config/**`, generated Avro/QueryDSL output), then sets it back with `classDirectories.setFrom(...)`. This affects only this report's analysis surface — it doesn't change what tests run, doesn't move thresholds, and (since this leaf is about the report) is the report-level way to scope coverage. Exclusions should be deliberate: hiding untested business logic to inflate numbers is gaming the metric, whereas excluding truly untestable generated code is legitimate. Patterns are matched against the `.class` file paths, so you use bytecode-style globs (slashes, `.class`, inner-class `$`).
code
kotlin · 15 linestasks.jacocoTestReport {
classDirectories.setFrom(
files(classDirectories.files.map {
fileTree(it) {
exclude(
"**/dto/**",
"**/config/**",
"**/generated/**",
"**/*MapperImpl.class"
)
}
})
)
reports { xml.required.set(true); html.required.set(true) }
}go deeper
Awareness that you can exclude classes from the report; exact syntax not expected.
Know you filter classDirectories with a fileTree + exclude and that patterns target .class paths.
Explain setFrom vs from, agent-time vs report-time exclusion, and the metric-gaming caveat.
Define an org policy/convention for legitimate exclusions and review it so coverage numbers stay honest and comparable across modules.
## Why exclude at all JaCoCo measures every class on its analysis path. Generated code (QueryDSL `Q*` types, MapStruct `*MapperImpl`, Avro/Protobuf classes), simple DTOs/entities with no logic, and framework config classes can have little or no test coverage and skew the percentage in a way that says nothing about test quality. Removing them from the **report** gives a number that reflects code you actually own and test. ## The mechanism: filter `classDirectories` `JacocoReport.classDirectories` is a `ConfigurableFileCollection` describing the compiled classes to analyze. By default the plugin wires it to the main source set's class output. You override it with a filtered tree: ```kotlin tasks.jacocoTestReport { classDirectories.setFrom( files(classDirectories.files.map { fileTree(it) { exclude( "**/dto/**", "**/config/**", "**/*MapperImpl.class", "**/generated/**" ) } }) ) } ``` Key points: - Patterns match the **`.class` file paths**, not source files — use `**/...` globs and the `.class` suffix; inner classes appear as `Outer$Inner.class`. - `setFrom(...)` *replaces* the collection; using `from(...)` would *add*, which isn't what you want here. - This is purely a **report** concern. It changes the analysis surface of `jacocoTestReport`, not test execution or any threshold task. ## Alternative: exclude at agent time You can also exclude during instrumentation via `test { jacoco { excludes = listOf("com/foo/generated/*") } }`, which keeps them out of the `.exec` data entirely. Report-side filtering is more common because it's per-report and doesn't require re-running tests to change the view. ## Governance caveat Exclusions are a double-edged sword. Legitimate: generated code, `package-info`, pure DTOs. Illegitimate: excluding hard-to-test services to make the dashboard green — that hides risk. In a shared convention plugin, keep the exclusion list small, reviewed, and consistent across modules. ## Result After filtering, the HTML/XML report (and anything that consumes the XML) reflects the reduced class set, so the headline percentage is computed only over the classes you chose to measure.
- Are the exclude patterns matched against source files or compiled classes?Against the compiled `.class` files on `classDirectories`, so you write bytecode-path globs (e.g. `**/*MapperImpl.class`, inner classes as `Outer$Inner.class`).
- Why use `setFrom(...)` rather than `from(...)` when filtering?`setFrom` replaces the existing collection with your filtered tree; `from` would add to it, leaving the unfiltered directories still in scope.
- What's the risk of over-using exclusions?Excluding genuinely testable business logic just to raise the percentage games the metric and hides untested risk; keep the list to truly untestable/generated code.
saying these in an interview costs you the question
- Writing exclude patterns as source paths (`**/*.java`) — they must target `.class` files.
- Using `from(...)` instead of `setFrom(...)`, which fails to actually remove the directories.