Background
We use JaCoCo (instruction coverage) with Scala. Scala emits large amounts of compiler boilerplate
(case-class helpers, lambdas, default-param accessors, value-class bridges, …), which inflates method
counts and distorts coverage %.
Today, filtering is done by the ABSA JaCoCo fork
za.co.absa.jacoco:jacoco-maven-plugin (doScalaMethodFiltration=true) configured in root-pom's
code-coverage profile. The fork is pinned at 0.8.11 and unmaintained, which also blocks moving
to a current stock JaCoCo.
The replacement is MoranaApps JMF — io.github.moranaapps:jacoco-method-filter-maven-plugin.
It rewrites compiled classes to add @CoverageGenerated to matched methods so stock JaCoCo
ignores them. Rules live in a jmf-rules.txt file; with no rules file the plugin is a pass-through
(unfiltered report, no failure), and strict enforcement is available via opt-in jmf.requireRules.
Use the JMF release ≥ the one carrying MoranaApps/jacoco-method-filter#70 (release TBD).
AbsaOSS/commons#147 first added JMF locally in Commons (108-line pom.xml profile override).
Review consensus (@wajda, @miroslavpojer): this is a common standard → it belongs in root-pom,
released as a new version, opt-in so projects adopt it explicitly. Commons then becomes the
first consumer and its PR shrinks to a parent bump + jmf-rules.txt + workflow.
Goal
root-pom's code-coverage profile supports JMF filtering, activated only when the consumer
opts in (by adding jmf-rules.txt). A project that does not opt in sees no behaviour change when
it bumps the parent — same coverage path, same numbers.
Why the JMF profile must be gated
The JMF path neutralizes the za.co.absa.jacoco fork (<phase>none</phase>) and switches to stock
org.jacoco 0.8.15. If it were always-on, a non-adopter with no jmf-rules.txt would silently lose
the fork's doScalaMethodFiltration and get unfiltered Scala coverage — a coverage-number shift
on a plain parent bump. Gating on jmf-rules.txt existence keeps "bump the parent, change nothing
else → identical numbers" true, and spares non-adopters the extra rewrite / report /
resources-overlay executions.
Design decisions to resolve
| # |
Decision |
Proposed default |
| D1 |
Activation mechanism |
Nested profile inside code-coverage + <activation><file><exists>${project.basedir}/jmf-rules.txt</exists></file></activation>. Fallback if it clashes with the existing <property><name>code-coverage</name></property> activation: jmf.skip property defaulting to true, adopter overrides via a pom property / -Djmf.skip=false. |
| D2 |
Default / global rules |
Ship a root-pom-maintained global rules set, passed via <globalRules> (bundled file path resolvable in every consumer, or a raw URL). jmf-rules.txt then only carries project-specific +/exclude rules. Trade-off: URL fetch at build time / offline builds. |
| D3 |
Fate of the za.co.absa.jacoco fork |
Keep as the default code-coverage path for non-adopters; inside the JMF path set its executions to <phase>none</phase> and use stock org.jacoco:jacoco-maven-plugin 0.8.15. Verify no version clash. |
| D4 |
root-pom version bump |
Minor if D1 keeps it fully backward compatible (nothing changes unless the file is added); major if any default behaviour shifts. |
Scope
Acceptance criteria
- A consumer that bumps the parent and does nothing else builds and reports coverage exactly as before.
- A consumer that adds
jmf-rules.txt gets a filtered HTML + XML report under target/.
mvn compile jacoco-method-filter:verify lists filtered methods for review.
- The migration guide is in the release notes.
Background
We use JaCoCo (instruction coverage) with Scala. Scala emits large amounts of compiler boilerplate
(case-class helpers, lambdas, default-param accessors, value-class bridges, …), which inflates method
counts and distorts coverage %.
Today, filtering is done by the ABSA JaCoCo fork
za.co.absa.jacoco:jacoco-maven-plugin(doScalaMethodFiltration=true) configured in root-pom'scode-coverageprofile. The fork is pinned at 0.8.11 and unmaintained, which also blocks movingto a current stock JaCoCo.
The replacement is MoranaApps JMF —
io.github.moranaapps:jacoco-method-filter-maven-plugin.It rewrites compiled classes to add
@CoverageGeneratedto matched methods so stock JaCoCoignores them. Rules live in a
jmf-rules.txtfile; with no rules file the plugin is a pass-through(unfiltered report, no failure), and strict enforcement is available via opt-in
jmf.requireRules.Use the JMF release ≥ the one carrying MoranaApps/jacoco-method-filter#70 (release TBD).
AbsaOSS/commons#147 first added JMF locally in Commons (108-line
pom.xmlprofile override).Review consensus (@wajda, @miroslavpojer): this is a common standard → it belongs in root-pom,
released as a new version, opt-in so projects adopt it explicitly. Commons then becomes the
first consumer and its PR shrinks to a parent bump +
jmf-rules.txt+ workflow.Goal
root-pom's
code-coverageprofile supports JMF filtering, activated only when the consumeropts in (by adding
jmf-rules.txt). A project that does not opt in sees no behaviour change whenit bumps the parent — same coverage path, same numbers.
Why the JMF profile must be gated
The JMF path neutralizes the
za.co.absa.jacocofork (<phase>none</phase>) and switches to stockorg.jacoco0.8.15. If it were always-on, a non-adopter with nojmf-rules.txtwould silently losethe fork's
doScalaMethodFiltrationand get unfiltered Scala coverage — a coverage-number shifton a plain parent bump. Gating on
jmf-rules.txtexistence keeps "bump the parent, change nothingelse → identical numbers" true, and spares non-adopters the extra
rewrite/report/resources-overlay executions.
Design decisions to resolve
code-coverage+<activation><file><exists>${project.basedir}/jmf-rules.txt</exists></file></activation>. Fallback if it clashes with the existing<property><name>code-coverage</name></property>activation:jmf.skipproperty defaulting totrue, adopter overrides via a pom property /-Djmf.skip=false.<globalRules>(bundled file path resolvable in every consumer, or a raw URL).jmf-rules.txtthen only carries project-specific+/exclude rules. Trade-off: URL fetch at build time / offline builds.za.co.absa.jacocoforkcode-coveragepath for non-adopters; inside the JMF path set its executions to<phase>none</phase>and use stockorg.jacoco:jacoco-maven-plugin0.8.15. Verify no version clash.Scope
code-coverage:za.co.absa.jacoco:jacoco-maven-pluginexecutions (<phase>none</phase>)org.jacoco:jacoco-maven-plugin0.8.15 —prepare-agent,propertyName=jacoco.argLineio.github.moranaapps:jacoco-method-filter-maven-plugin<pinned release> —rewrite@process-test-classes,report@verify(reportFormats=html,xml)maven-resources-plugin— overlay${project.build.directory}/classes-filteredoverclasses@process-test-classesorg.scalatest:scalatest-maven-plugin—<argLine>${jacoco.argLine}</argLine>mvn installa SNAPSHOT; run the three test scenarios:jmf-rules.txt+-Pcode-coverage→ old coverage path, no failure, numbers unchanged.jmf-rules.txt→rewrite+ filteredreportrun,target/jacoco.xmlproduced, boilerplate filtered.SPEC.md§7) intodocs/and the GitHub release.release/X.Y.Z; verify on Maven Central.Acceptance criteria
jmf-rules.txtgets a filtered HTML + XML report undertarget/.mvn compile jacoco-method-filter:verifylists filtered methods for review.