You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by as...@apache.org on 2026/09/08 15:24:18 UTC
(maven) 01/02: Fix #13068: make PluginDependenciesResolver methods default
This is an automated email from the ASF dual-hosted git repository.
ascheman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven.git
commit 7aded6b06e30c8696ba0a97f1826d7312b56eb66
Author: Gerd Aschemann <as...@apache.org>
AuthorDate: Sat Sep 5 15:09:02 2026 +0200
Fix #13068: make PluginDependenciesResolver methods default
resolveCoreExtensionAndFlatten and resolvePluginAndFlatten were added as
abstract methods in 4.0.0-rc-6, forward-porting the Maven 3.10.0 changes
(#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 349395c0c9 from maven-4.0.x. The breaking change was
ported to both lines (#12329 master, #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 #13068
Co-Authored-By: Claude Opus 5 (1M context) <no...@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W2HnhKt7MJWYsSSVUmPrvU
---
.../internal/PluginDependenciesResolver.java | 26 +++-
...uginDependenciesResolverDefaultMethodsTest.java | 154 +++++++++++++++++++++
...h13068LegacyPluginDependenciesResolverTest.java | 63 +++++++++
.../client/.mvn/extensions.xml | 26 ++++
.../client/pom.xml | 33 +++++
.../extension/pom.xml | 79 +++++++++++
.../gh13068/LegacyPluginDependenciesResolver.java | 90 ++++++++++++
7 files changed, 467 insertions(+), 4 deletions(-)
diff --git a/impl/maven-core/src/main/java/org/apache/maven/plugin/internal/PluginDependenciesResolver.java b/impl/maven-core/src/main/java/org/apache/maven/plugin/internal/PluginDependenciesResolver.java
index b3f02e39f3..20d8fb88c6 100644
--- a/impl/maven-core/src/main/java/org/apache/maven/plugin/internal/PluginDependenciesResolver.java
+++ b/impl/maven-core/src/main/java/org/apache/maven/plugin/internal/PluginDependenciesResolver.java
@@ -74,6 +74,12 @@ DependencyNode resolve(
/**
* Resolves the runtime dependencies of the specified core extension (as {@link Plugin} as GAV carrier).
+ * <p>
+ * The default implementation delegates to
+ * {@link #resolvePlugin(Plugin, Artifact, DependencyFilter, List, RepositorySystemSession)} so that
+ * implementations written against Maven 4.0.0-rc-5 and earlier keep working. Implementations should override
+ * this method: unlike the default, a dedicated implementation is expected to read the extension's artifact
+ * descriptor first, applying relocations and dependency validation.
*
* @param plugin The plugin for which to resolve the dependencies, must not be {@code null}.
* @param dependencyFilter A filter to exclude artifacts from resolution (but not collection), may be {@code null}.
@@ -83,15 +89,21 @@ DependencyNode resolve(
* @throws PluginResolutionException If any dependency could not be resolved.
* @since 3.10.0
*/
- DependencyResult resolveCoreExtensionAndFlatten(
+ default DependencyResult resolveCoreExtensionAndFlatten(
Plugin plugin,
DependencyFilter dependencyFilter,
List<RemoteRepository> repositories,
RepositorySystemSession session)
- throws PluginResolutionException;
+ throws PluginResolutionException {
+ return resolvePlugin(plugin, null, dependencyFilter, repositories, session);
+ }
/**
* Resolves the runtime dependencies of the specified plugin.
+ * <p>
+ * Implementations must not delegate to
+ * {@link #resolvePluginAndFlatten(Plugin, Artifact, DependencyFilter, List, RepositorySystemSession)} without
+ * also overriding it: the default implementation of that method delegates back here.
*
* @param plugin The plugin for which to resolve the dependencies, must not be {@code null}.
* @param artifact The plugin's main artifact, may be {@code null}.
@@ -114,6 +126,10 @@ DependencyResult resolvePlugin(
/**
* Resolves the runtime dependencies of the specified plugin.
+ * <p>
+ * The default implementation delegates to
+ * {@link #resolvePlugin(Plugin, Artifact, DependencyFilter, List, RepositorySystemSession)}, which this method
+ * supersedes, so that implementations written against Maven 4.0.0-rc-5 and earlier keep working.
*
* @param plugin The plugin for which to resolve the dependencies, must not be {@code null}.
* @param pluginArtifact The plugin's main artifact, may be {@code null}.
@@ -124,11 +140,13 @@ DependencyResult resolvePlugin(
* @throws PluginResolutionException If any dependency could not be resolved.
* @since 3.10.0
*/
- DependencyResult resolvePluginAndFlatten(
+ default DependencyResult resolvePluginAndFlatten(
Plugin plugin,
Artifact pluginArtifact,
DependencyFilter dependencyFilter,
List<RemoteRepository> repositories,
RepositorySystemSession session)
- throws PluginResolutionException;
+ throws PluginResolutionException {
+ return resolvePlugin(plugin, pluginArtifact, dependencyFilter, repositories, session);
+ }
}
diff --git a/impl/maven-core/src/test/java/org/apache/maven/plugin/internal/PluginDependenciesResolverDefaultMethodsTest.java b/impl/maven-core/src/test/java/org/apache/maven/plugin/internal/PluginDependenciesResolverDefaultMethodsTest.java
new file mode 100644
index 0000000000..2d5d933b57
--- /dev/null
+++ b/impl/maven-core/src/test/java/org/apache/maven/plugin/internal/PluginDependenciesResolverDefaultMethodsTest.java
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.plugin.internal;
+
+import java.lang.reflect.Method;
+import java.util.List;
+
+import org.apache.maven.model.Plugin;
+import org.apache.maven.plugin.PluginResolutionException;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.artifact.DefaultArtifact;
+import org.eclipse.aether.graph.DependencyFilter;
+import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.aether.resolution.DependencyRequest;
+import org.eclipse.aether.resolution.DependencyResult;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * {@link PluginDependenciesResolver} is documented as internal, but tools that embed Maven — most visibly
+ * IntelliJ IDEA's {@code Maven40PluginDependenciesResolver} — implement it out of tree and are compiled against
+ * one Maven version while running against another. When {@code resolveCoreExtensionAndFlatten} and
+ * {@code resolvePluginAndFlatten} were forward-ported from Maven 3.10.0 as <em>abstract</em> methods, every such
+ * implementation started failing with {@code AbstractMethodError} as soon as core invoked them.
+ *
+ * <p>These tests pin the compatibility contract: an implementation providing only the methods that existed before
+ * the forward port must remain a legal implementation, and the two newer methods must stay {@code default}.
+ */
+class PluginDependenciesResolverDefaultMethodsTest {
+
+ private static final DependencyResult RESULT = new DependencyResult(new DependencyRequest());
+
+ /**
+ * Implements exactly the method set of the pre-forward-port interface — nothing more. This class failing to
+ * compile <em>is</em> the regression: it means the two newer methods went back to being abstract.
+ */
+ private static final class LegacyResolver implements PluginDependenciesResolver {
+
+ private Plugin plugin;
+ private Artifact pluginArtifact;
+ private DependencyFilter dependencyFilter;
+ private List<RemoteRepository> repositories;
+ private RepositorySystemSession session;
+
+ @Override
+ public Artifact resolve(Plugin plugin, List<RemoteRepository> repositories, RepositorySystemSession session) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public org.eclipse.aether.graph.DependencyNode resolve(
+ Plugin plugin,
+ Artifact pluginArtifact,
+ DependencyFilter dependencyFilter,
+ List<RemoteRepository> repositories,
+ RepositorySystemSession session) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public DependencyResult resolvePlugin(
+ Plugin plugin,
+ Artifact pluginArtifact,
+ DependencyFilter dependencyFilter,
+ List<RemoteRepository> repositories,
+ RepositorySystemSession session) {
+ this.plugin = plugin;
+ this.pluginArtifact = pluginArtifact;
+ this.dependencyFilter = dependencyFilter;
+ this.repositories = repositories;
+ this.session = session;
+ return RESULT;
+ }
+ }
+
+ @Test
+ void resolvePluginAndFlattenDelegatesToResolvePlugin() throws PluginResolutionException {
+ LegacyResolver resolver = new LegacyResolver();
+ Plugin plugin = new Plugin();
+ Artifact artifact = new DefaultArtifact("g:a:1.0");
+ DependencyFilter filter = (node, parents) -> true;
+ List<RemoteRepository> repositories = List.of();
+
+ assertSame(RESULT, resolver.resolvePluginAndFlatten(plugin, artifact, filter, repositories, null));
+
+ assertSame(plugin, resolver.plugin);
+ assertSame(artifact, resolver.pluginArtifact);
+ assertSame(filter, resolver.dependencyFilter);
+ assertSame(repositories, resolver.repositories);
+ assertNull(resolver.session);
+ }
+
+ @Test
+ void resolveCoreExtensionAndFlattenDelegatesToResolvePlugin() throws PluginResolutionException {
+ LegacyResolver resolver = new LegacyResolver();
+ Plugin plugin = new Plugin();
+ DependencyFilter filter = (node, parents) -> true;
+ List<RemoteRepository> repositories = List.of();
+
+ assertSame(RESULT, resolver.resolveCoreExtensionAndFlatten(plugin, filter, repositories, null));
+
+ 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);
+ }
+
+ /**
+ * Guards the property that actually broke IntelliJ IDEA: these methods are invoked on implementations compiled
+ * against an older Maven, so they must carry an implementation in the interface itself. Turning either back into
+ * an abstract method reintroduces {@code AbstractMethodError} for every out-of-tree implementation.
+ */
+ @Test
+ void newerMethodsAreDefaultMethods() throws NoSuchMethodException {
+ Method resolveCoreExtensionAndFlatten = PluginDependenciesResolver.class.getMethod(
+ "resolveCoreExtensionAndFlatten",
+ Plugin.class,
+ DependencyFilter.class,
+ List.class,
+ RepositorySystemSession.class);
+ Method resolvePluginAndFlatten = PluginDependenciesResolver.class.getMethod(
+ "resolvePluginAndFlatten",
+ Plugin.class,
+ Artifact.class,
+ DependencyFilter.class,
+ List.class,
+ RepositorySystemSession.class);
+
+ assertTrue(
+ resolveCoreExtensionAndFlatten.isDefault(),
+ "resolveCoreExtensionAndFlatten must stay a default method");
+ assertTrue(resolvePluginAndFlatten.isDefault(), "resolvePluginAndFlatten must stay a default method");
+ }
+}
diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh13068LegacyPluginDependenciesResolverTest.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh13068LegacyPluginDependenciesResolverTest.java
new file mode 100644
index 0000000000..1c55e2cfda
--- /dev/null
+++ b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh13068LegacyPluginDependenciesResolverTest.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.it;
+
+import java.nio.file.Path;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Regression test for <a href="https://github.com/apache/maven/issues/13068">apache/maven#13068</a>.
+ * The forward port in <a href="https://github.com/apache/maven/pull/12329">apache/maven#12329</a> added
+ * {@code resolveCoreExtensionAndFlatten} and {@code resolvePluginAndFlatten} to
+ * {@code PluginDependenciesResolver} as abstract methods.
+ * <p>
+ * That 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. Because such
+ * implementations live out of tree and are compiled against one Maven while running on another, adding
+ * abstract methods turns every plugin resolution into an {@code AbstractMethodError}.
+ * <p>
+ * This test builds a core extension against maven-core 4.0.0-rc-5 — the last release before those methods
+ * existed — installs it, and then runs a build that uses it. If the newer interface methods are abstract,
+ * the build fails before any goal executes.
+ */
+class MavenITgh13068LegacyPluginDependenciesResolverTest extends AbstractMavenIntegrationTestCase {
+
+ @Test
+ void legacyImplementationStillWorks() throws Exception {
+ Path testDir = extractResources("gh-13068-legacy-plugin-dependencies-resolver");
+
+ Verifier extensionVerifier = newVerifier(testDir.resolve("extension"));
+ extensionVerifier.deleteArtifacts("org.apache.maven.its.gh-13068");
+ extensionVerifier.addCliArgument("install");
+ extensionVerifier.execute();
+ extensionVerifier.verifyErrorFreeLog();
+
+ Verifier clientVerifier = newVerifier(testDir.resolve("client"));
+ clientVerifier.setAutoclean(false);
+ clientVerifier.addCliArgument("clean");
+ clientVerifier.execute();
+ clientVerifier.verifyErrorFreeLog();
+
+ // the extension must actually have displaced the default component, otherwise this test
+ // would pass without ever exercising the interface
+ clientVerifier.verifyTextInLog("[gh-13068] legacy PluginDependenciesResolver installed");
+ clientVerifier.verifyTextInLog("[gh-13068] resolvePlugin reached for maven-clean-plugin");
+ }
+}
diff --git a/its/core-it-suite/src/test/resources/gh-13068-legacy-plugin-dependencies-resolver/client/.mvn/extensions.xml b/its/core-it-suite/src/test/resources/gh-13068-legacy-plugin-dependencies-resolver/client/.mvn/extensions.xml
new file mode 100644
index 0000000000..da9e208285
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/gh-13068-legacy-plugin-dependencies-resolver/client/.mvn/extensions.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<extensions>
+ <extension>
+ <groupId>org.apache.maven.its.gh-13068</groupId>
+ <artifactId>legacy-plugin-dependencies-resolver</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ </extension>
+</extensions>
diff --git a/its/core-it-suite/src/test/resources/gh-13068-legacy-plugin-dependencies-resolver/client/pom.xml b/its/core-it-suite/src/test/resources/gh-13068-legacy-plugin-dependencies-resolver/client/pom.xml
new file mode 100644
index 0000000000..853bc1e170
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/gh-13068-legacy-plugin-dependencies-resolver/client/pom.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>org.apache.maven.its.gh-13068</groupId>
+ <artifactId>client</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ <packaging>jar</packaging>
+
+ <name>Maven Integration Test :: gh-13068 :: client</name>
+ <description>
+ Builds with a core extension that overrides PluginDependenciesResolver with a pre-4.0.0-rc-6
+ implementation. Resolving any plugin routes through that extension.
+ </description>
+</project>
diff --git a/its/core-it-suite/src/test/resources/gh-13068-legacy-plugin-dependencies-resolver/extension/pom.xml b/its/core-it-suite/src/test/resources/gh-13068-legacy-plugin-dependencies-resolver/extension/pom.xml
new file mode 100644
index 0000000000..f92eb8a5b2
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/gh-13068-legacy-plugin-dependencies-resolver/extension/pom.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>org.apache.maven.its.gh-13068</groupId>
+ <artifactId>legacy-plugin-dependencies-resolver</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ <packaging>jar</packaging>
+
+ <name>Maven Integration Test :: legacy-plugin-dependencies-resolver</name>
+ <description>
+ A core extension that overrides PluginDependenciesResolver implementing only the method set that
+ existed up to Maven 4.0.0-rc-5. It is deliberately compiled against maven-core 4.0.0-rc-5, the last
+ release before resolveCoreExtensionAndFlatten and resolvePluginAndFlatten were added, so that it
+ reproduces the situation every out-of-tree implementation is in: built against one Maven, run on another.
+ </description>
+
+ <properties>
+ <maven.compiler.release>17</maven.compiler.release>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-core</artifactId>
+ <version>4.0.0-rc-5</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.inject</groupId>
+ <artifactId>javax.inject</artifactId>
+ <version>1</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.sisu</groupId>
+ <artifactId>org.eclipse.sisu.inject</artifactId>
+ <version>1.1.0</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.eclipse.sisu</groupId>
+ <artifactId>sisu-maven-plugin</artifactId>
+ <version>1.1.0</version>
+ <executions>
+ <execution>
+ <id>index-project</id>
+ <goals>
+ <goal>main-index</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/its/core-it-suite/src/test/resources/gh-13068-legacy-plugin-dependencies-resolver/extension/src/main/java/org/apache/maven/its/gh13068/LegacyPluginDependenciesResolver.java b/its/core-it-suite/src/test/resources/gh-13068-legacy-plugin-dependencies-resolver/extension/src/main/java/org/apache/maven/its/gh13068/LegacyPluginDependenciesResolver.java
new file mode 100644
index 0000000000..8755935d93
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/gh-13068-legacy-plugin-dependencies-resolver/extension/src/main/java/org/apache/maven/its/gh13068/LegacyPluginDependenciesResolver.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.its.gh13068;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import java.util.List;
+
+import org.apache.maven.model.Plugin;
+import org.apache.maven.plugin.PluginResolutionException;
+import org.apache.maven.plugin.internal.DefaultPluginDependenciesResolver;
+import org.apache.maven.plugin.internal.PluginDependenciesResolver;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.graph.DependencyFilter;
+import org.eclipse.aether.graph.DependencyNode;
+import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.aether.resolution.DependencyResult;
+import org.eclipse.sisu.Priority;
+
+/**
+ * A decorating {@link PluginDependenciesResolver} that implements only the methods which existed up to
+ * Maven 4.0.0-rc-5, delegating everything to the default implementation. This mirrors how IDEs embed
+ * Maven, most visibly IntelliJ IDEA's {@code Maven40PluginDependenciesResolver}.
+ * <p>
+ * Compiled against maven-core 4.0.0-rc-5 (see this module's POM) and run against the Maven under test,
+ * so the newer interface methods have no implementation here. They must therefore be resolved through
+ * {@code default} methods on the interface; if they are abstract, plugin resolution dies with
+ * {@code AbstractMethodError} before a single goal runs.
+ */
+@Named
+@Singleton
+@Priority(10)
+public class LegacyPluginDependenciesResolver implements PluginDependenciesResolver {
+
+ private final PluginDependenciesResolver delegate;
+
+ @Inject
+ public LegacyPluginDependenciesResolver(DefaultPluginDependenciesResolver delegate) {
+ this.delegate = delegate;
+ System.out.println("[gh-13068] legacy PluginDependenciesResolver installed");
+ }
+
+ @Override
+ public Artifact resolve(Plugin plugin, List<RemoteRepository> repositories, RepositorySystemSession session)
+ throws PluginResolutionException {
+ return delegate.resolve(plugin, repositories, session);
+ }
+
+ @Override
+ public DependencyNode resolve(
+ Plugin plugin,
+ Artifact pluginArtifact,
+ DependencyFilter dependencyFilter,
+ List<RemoteRepository> repositories,
+ RepositorySystemSession session)
+ throws PluginResolutionException {
+ return delegate.resolve(plugin, pluginArtifact, dependencyFilter, repositories, session);
+ }
+
+ @Override
+ public DependencyResult resolvePlugin(
+ Plugin plugin,
+ Artifact pluginArtifact,
+ DependencyFilter dependencyFilter,
+ List<RemoteRepository> repositories,
+ RepositorySystemSession session)
+ throws PluginResolutionException {
+ System.out.println("[gh-13068] resolvePlugin reached for " + plugin.getArtifactId());
+ return delegate.resolvePlugin(plugin, pluginArtifact, dependencyFilter, repositories, session);
+ }
+}