This is an automated email from the ASF dual-hosted git repository. gnodet pushed a commit to branch ci-issue-11856 in repository https://gitbox.apache.org/repos/asf/maven.git
commit d8b4c1f220b2c160b36e987aba9ffc16721b6cff Author: Guillaume Nodet <[email protected]> AuthorDate: Fri Apr 3 22:28:02 2026 +0200 Fix #11856: Improve error message for prefix-based remote repository filtering errors Co-Authored-By: Claude Opus 4.6 <[email protected]> --- .../maven/exception/DefaultExceptionHandler.java | 38 +++++++++++++++++++++- .../exception/DefaultExceptionHandlerTest.java | 35 ++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/impl/maven-core/src/main/java/org/apache/maven/exception/DefaultExceptionHandler.java b/impl/maven-core/src/main/java/org/apache/maven/exception/DefaultExceptionHandler.java index 98db980812..297a8b4fd8 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/exception/DefaultExceptionHandler.java +++ b/impl/maven-core/src/main/java/org/apache/maven/exception/DefaultExceptionHandler.java @@ -40,6 +40,7 @@ import org.apache.maven.plugin.PluginExecutionException; import org.apache.maven.project.ProjectBuildingException; import org.apache.maven.project.ProjectBuildingResult; +import org.eclipse.aether.transfer.ArtifactFilteredOutException; /* @@ -177,6 +178,9 @@ private String getReference(Set<Throwable> dejaVu, Throwable exception) { reference = ConnectException.class.getSimpleName(); } } + if (findCause(exception, ArtifactFilteredOutException.class) != null) { + reference = "https://maven.apache.org/resolver/remote-repository-filtering.html"; + } } else if (exception instanceof LinkageError) { reference = LinkageError.class.getSimpleName(); } else if (exception instanceof PluginExecutionException) { @@ -207,7 +211,9 @@ private String getReference(Set<Throwable> dejaVu, Throwable exception) { } } - if ((reference != null && !reference.isEmpty()) && !reference.startsWith("http:")) { + if ((reference != null && !reference.isEmpty()) + && !reference.startsWith("http:") + && !reference.startsWith("https:")) { reference = "http://cwiki.apache.org/confluence/display/MAVEN/" + reference; } @@ -229,6 +235,8 @@ private boolean isNoteworthyException(Throwable exception) { private String getMessage(String message, Throwable exception) { String fullMessage = (message != null) ? message : ""; + boolean hasArtifactFilteredOut = false; + // To break out of possible endless loop when getCause returns "this", or dejaVu for n-level recursion (n>1) Set<Throwable> dejaVu = Collections.newSetFromMap(new IdentityHashMap<>()); for (Throwable t = exception; t != null && t != t.getCause(); t = t.getCause()) { @@ -260,15 +268,43 @@ private String getMessage(String message, Throwable exception) { fullMessage = join(fullMessage, exceptionMessage); } + if (t instanceof ArtifactFilteredOutException) { + hasArtifactFilteredOut = true; + } + if (!dejaVu.add(t)) { fullMessage = join(fullMessage, "[CIRCULAR REFERENCE]"); break; } } + if (hasArtifactFilteredOut) { + fullMessage += System.lineSeparator() + + System.lineSeparator() + + "This error indicates that the remote repository's prefix file does not list" + + " this artifact's group. This commonly happens with repository managers" + + " using virtual/group repositories that do not properly aggregate prefix files." + + System.lineSeparator() + + "To disable prefix-based filtering, add" + + " -Daether.remoteRepositoryFilter.prefixes=false" + + " to your command line or to .mvn/maven.config." + + System.lineSeparator() + + "See https://maven.apache.org/resolver/remote-repository-filtering.html"; + } + return fullMessage.trim(); } + private static <T extends Throwable> T findCause(Throwable exception, Class<T> type) { + Set<Throwable> dejaVu = Collections.newSetFromMap(new IdentityHashMap<>()); + for (Throwable t = exception; t != null && dejaVu.add(t); t = t.getCause()) { + if (type.isInstance(t)) { + return type.cast(t); + } + } + return null; + } + private String join(String message1, String message2) { String message = ""; diff --git a/impl/maven-core/src/test/java/org/apache/maven/exception/DefaultExceptionHandlerTest.java b/impl/maven-core/src/test/java/org/apache/maven/exception/DefaultExceptionHandlerTest.java index c9a84014d2..ff72133451 100644 --- a/impl/maven-core/src/test/java/org/apache/maven/exception/DefaultExceptionHandlerTest.java +++ b/impl/maven-core/src/test/java/org/apache/maven/exception/DefaultExceptionHandlerTest.java @@ -29,9 +29,15 @@ import org.apache.maven.plugin.PluginExecutionException; import org.apache.maven.plugin.descriptor.MojoDescriptor; import org.apache.maven.plugin.descriptor.PluginDescriptor; +import org.eclipse.aether.artifact.DefaultArtifact; +import org.eclipse.aether.repository.RemoteRepository; +import org.eclipse.aether.resolution.ArtifactResolutionException; +import org.eclipse.aether.resolution.ArtifactResult; +import org.eclipse.aether.transfer.ArtifactFilteredOutException; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; /** */ @@ -124,6 +130,35 @@ public synchronized Throwable getCause() { assertEquals(expectedReference, summary.getReference()); } + @Test + void testArtifactFilteredOutException() { + RemoteRepository repo = + new RemoteRepository.Builder("my-repo", "default", "https://repo.example.com/maven").build(); + ArtifactFilteredOutException filterEx = new ArtifactFilteredOutException( + new DefaultArtifact("com.example:my-lib:jar:1.0"), + repo, + "Prefix com/example/my-lib/1.0/my-lib-1.0.jar NOT allowed from my-repo" + + " (https://repo.example.com/maven, default, releases)"); + ArtifactResult artifactResult = new ArtifactResult(new org.eclipse.aether.resolution.ArtifactRequest( + new DefaultArtifact("com.example:my-lib:jar:1.0"), java.util.List.of(repo), null)); + artifactResult.addException(filterEx); + ArtifactResolutionException resolutionEx = + new ArtifactResolutionException(java.util.List.of(artifactResult), "Could not resolve artifact"); + MojoExecutionException mojoEx = new MojoExecutionException("Resolution failed", resolutionEx); + + DefaultExceptionHandler handler = new DefaultExceptionHandler(); + ExceptionSummary summary = handler.handleException(mojoEx); + + assertTrue( + summary.getMessage().contains("-Daether.remoteRepositoryFilter.prefixes=false"), + "Message should contain the workaround property"); + assertTrue(summary.getMessage().contains("prefix file"), "Message should explain the prefix file cause"); + assertEquals( + "https://maven.apache.org/resolver/remote-repository-filtering.html", + summary.getReference(), + "Reference should point to the RRF documentation"); + } + @Test void testHandleExceptionSelfReferencing() { RuntimeException boom3 = new RuntimeException("BOOM3");
