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);
+    }
+}