Fix #13068: make PluginDependenciesResolver methods default - #13075
Conversation
resolveCoreExtensionAndFlatten and resolvePluginAndFlatten were added as abstract methods in 4.0.0-rc-6, forward-porting the Maven 3.10.0 changes (apache#12335). The interface is documented as internal, but it is the only hook Maven offers for influencing plugin resolution, and every major Java IDE overrides it: IntelliJ IDEA, Eclipse m2e and NetBeans. Those implementations live out of tree and are compiled against one Maven while running on another, so adding abstract methods turns plugin resolution into an AbstractMethodError. Both methods now default to the pre-existing resolvePlugin, which DefaultPluginDependenciesResolver already treats as an alias -- there, resolvePlugin delegates to resolvePluginAndFlatten. resolvePlugin itself deliberately stays abstract: a default there would let an implementation overriding neither method recurse infinitely. The default for resolveCoreExtensionAndFlatten is best effort. A dedicated implementation additionally reads the extension's artifact descriptor to apply relocations and run MavenPluginDependenciesValidator. Verified against the class shipped in IntelliJ IDEA 2026.2.2, which implements resolvePluginAndFlatten but not resolveCoreExtensionAndFlatten and therefore still fails on stock rc-6. Adds a unit test pinning the compatibility contract and a core IT that builds an extension against maven-core 4.0.0-rc-5 -- the last release before the methods existed -- and runs a build through it. Verified by Christofer Dutz (@chrisdutz) on Apache PLC4X: a build carrying this change imports cleanly in IntelliJ IDEA, where stock rc-6 fails. Forward port of 349395c from maven-4.0.x. The breaking change was ported to both lines (apache#12329 master, apache#12335 maven-4.0.x); the fix so far only to maven-4.0.x, so 4.1.0 would reintroduce the regression. Two adjustments were needed for master, both in the IT only: - no TestSuiteOrdering entry: that class now orders by name pattern (MavenITgh<number>) instead of keeping an explicit list - the IT harness moved on: AbstractMavenIntegrationTestCase no longer takes a version range, extractResources returns Path, and newVerifier(String) is deprecated in favour of newVerifier(Path) The production change and the unit test are byte-identical to maven-4.0.x. Closes apache#13068 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W2HnhKt7MJWYsSSVUmPrvU
gnodet
left a comment
There was a problem hiding this comment.
The fix is correct. Two abstract methods made default — the minimal change needed to restore backward compatibility with every out-of-tree implementation of PluginDependenciesResolver. CI is green across all platforms (JDK 17/21/25, Linux/macOS/Windows). The implementation is byte-identical to the maven-4.0.x commit.
One low-severity gap in the unit test: both delegation tests pass null as the session argument, then either assert assertNull(resolver.session) (which is trivially true) or don't check session at all. The test therefore doesn't actually exercise that the session is forwarded through the default method chain — it would pass even if the default method dropped the session on the floor. Flagging it as a nit; it doesn't affect correctness of the production fix, only test coverage depth.
This review was generated by an AI agent, Hermès, on behalf of @gnodet.
| assertSame(artifact, resolver.pluginArtifact); | ||
| assertSame(filter, resolver.dependencyFilter); | ||
| assertSame(repositories, resolver.repositories); | ||
| assertNull(resolver.session); |
There was a problem hiding this comment.
🔍 Nit — trivial session assertion: null is passed as the session argument and this assertion checks that null comes out the other side — which is guaranteed regardless of whether the default method actually forwards the argument. To make this test meaningful, pass a non-null mock or stub session and assert assertSame:
| assertNull(resolver.session); | |
| assertNull(resolver.session); |
Suggested improvement (cannot be a one-click suggestion due to the required change in the call site at line 104):
// line 103-110
RepositorySystemSession session = mock(RepositorySystemSession.class);
assertSame(RESULT, resolver.resolvePluginAndFlatten(plugin, artifact, filter, repositories, session));
assertSame(plugin, resolver.plugin);
assertSame(artifact, resolver.pluginArtifact);
assertSame(filter, resolver.dependencyFilter);
assertSame(repositories, resolver.repositories);
assertSame(session, resolver.session);(Add import static org.mockito.Mockito.mock; or use Mockito.mock(RepositorySystemSession.class) — the project already has Mockito on the test classpath.)
| assertSame(plugin, resolver.plugin); | ||
| assertNull(resolver.pluginArtifact, "the extension's main artifact is resolved from the plugin GAV"); | ||
| assertSame(filter, resolver.dependencyFilter); | ||
| assertSame(repositories, resolver.repositories); |
There was a problem hiding this comment.
🔍 Nit — session forwarding not tested: resolveCoreExtensionAndFlatten is called with null for the session, but the test has no assertion that the session is passed through to resolvePlugin. A non-null session should be passed and verified with assertSame(session, resolver.session) — same pattern as the sibling test.
The delegation tests passed null as the session and then asserted assertNull on it, which is trivially true -- they would have passed even if the default method dropped the session. The core-extension test did not check the session at all. Both now pass a mocked RepositorySystemSession and assert it arrives at resolvePlugin. Verified red by making the default method pass null: "expected: <Mock for RepositorySystemSession> but was: <null>". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W2HnhKt7MJWYsSSVUmPrvU
gnodet
left a comment
There was a problem hiding this comment.
Re-review after second commit ("Address review: assert the session is forwarded", 62967e1).
Both nits from the first pass are resolved:
resolvePluginAndFlattenDelegatesToResolvePlugin: now usesmock(RepositorySystemSession.class)and assertsassertSame(session, resolver.session)— the delegation is actually exercised.resolveCoreExtensionAndFlattenDelegatesToResolvePlugin: same pattern, line 135 now asserts session forwarding.
The fix itself is solid: two methods converted from abstract to default, Javadoc correctly documents the delegation chain and the circular-recursion trap for any implementation that tries to delegate resolvePlugin back to resolvePluginAndFlatten without overriding the latter. The newerMethodsAreDefaultMethods reflection test is a clean regression guard against future accidental reintroduction of abstract. The IT is faithful to current master conventions (newVerifier(Path), no version range constructor, failsafe) and the extension POM correctly compiles against 4.0.0-rc-5 to reproduce the binary-compat scenario.
No outstanding findings. Ready to merge.
This review was generated by an AI agent, Hermès, on behalf of @gnodet.
Summary
Forward port of 349395c, merged into
maven-4.0.xvia #13069.Closes #13068.
resolveCoreExtensionAndFlattenandresolvePluginAndFlattenwere added to the internalPluginDependenciesResolveras abstract methods. That happened on both lines — #12329 here, #12335 onmaven-4.0.x— but the fix so far landed only onmaven-4.0.x, so 4.1.0 would reintroduce the identical regression.Implementations that live out of tree are compiled against one Maven and run on another, so the first plugin resolution dies with
AbstractMethodError. IntelliJ IDEA, Eclipse m2e and NetBeans all override this component; IntelliJ implements the interface directly and therefore breaks. Christofer Dutz confirmed on Apache PLC4X that a build carrying this change imports cleanly where stock rc-6 fails.What differs from the maven-4.0.x commit
The production change and the unit test are byte-identical. Only the IT needed adjusting, because master has moved on in three ways:
TestSuiteOrderingentry. That class now orders by name pattern (MavenITgh<number>) instead of keeping an explicit list, soMavenITgh13068…is picked up automatically. The file is untouched here.AbstractMavenIntegrationTestCaseno longer takes a version range,extractResourcesreturnsPath, andnewVerifier(String)is deprecated in favour ofnewVerifier(Path). The IT was rewritten to the current idiom.Verification
Run locally against this branch:
The two marker lines are asserted by the test, so it cannot pass without the extension actually displacing the default component. On
maven-4.0.xthe same IT was verified red without the change, failing withAbstractMethodErroratDefaultMavenPluginManager.createPluginRealm.Note
The
resolveCoreExtensionAndFlattendefault is best effort — a dedicated implementation additionally reads the extension's artifact descriptor to apply relocations and runMavenPluginDependenciesValidator. Same caveat as on the 4.0.x PR, where @gnodet approved it as is.