skip to content

Generated and DTO classes are dragging down your JaCoCo numbers. How do you exclude them from the jacocoTestReport without touching coverage thresholds?

level: seniorimportance: should knowfreq 35%

answer

  1. filter classDirectories
  2. fileTree + exclude globs
  3. patterns match .class paths
  4. setFrom replaces, from adds
  5. exclude generated/DTO/config, not real logic

basics

~10 s

Override 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 lines
kotlin
tasks.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

for a junior

Awareness that you can exclude classes from the report; exact syntax not expected.

for a middle

Know you filter classDirectories with a fileTree + exclude and that patterns target .class paths.

for a senior

Explain setFrom vs from, agent-time vs report-time exclusion, and the metric-gaming caveat.

for a principal

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.

context