From d1c9490effc6553c509f9593d36b12b2a7d2bfd0 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Thu, 4 Jun 2026 17:55:49 +0200 Subject: [PATCH] Fix #11856: Improve error message for prefix-based remote repository filtering errors (#11888) * Fix #11856: Improve error message for prefix-based remote repository filtering errors Co-Authored-By: Claude Opus 4.6 * Fix #11856: Mention both prefixes and groupId filters in error message The ArtifactFilteredOutException can be triggered by either the prefixes or the groupId remote repository filter. Update the error message to mention both properties so users know which to disable. Co-Authored-By: Claude Opus 4.6 * Refactor: Convert error message string concatenation to text block Replace multi-line string concatenation with System.lineSeparator() calls with a cleaner text block in DefaultExceptionHandler. The error message for ArtifactFilteredOutException now uses Java text blocks (triple-quoted strings) instead of the + operator, improving readability and maintainability. Co-Authored-By: Claude Sonnet 4.5 --------- Co-authored-by: Claude Opus 4.6 Co-authored-by: Claude Code --- .../exception/DefaultExceptionHandler.java | 34 +++++++++++++++- .../DefaultExceptionHandlerTest.java | 40 +++++++++++++++++++ 2 files changed, 73 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 98db9808123c..fdf2441becac 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 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 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 dejaVu = Collections.newSetFromMap(new IdentityHashMap<>()); for (Throwable t = exception; t != null && t != t.getCause(); t = t.getCause()) { @@ -260,15 +268,39 @@ 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 += """ + + This error indicates that a remote repository filter has rejected this artifact. This commonly happens with repository managers using virtual/group repositories that do not properly aggregate prefix files. + To disable remote repository filtering, add one or both of these to your command line or to .mvn/maven.config: + -Daether.remoteRepositoryFilter.prefixes=false + -Daether.remoteRepositoryFilter.groupId=false + See https://maven.apache.org/resolver/remote-repository-filtering.html"""; + } + return fullMessage.trim(); } + private static T findCause(Throwable exception, Class type) { + Set 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 c9a84014d208..88359e2e8f6b 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,40 @@ 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 prefixes workaround property"); + assertTrue( + summary.getMessage().contains("-Daether.remoteRepositoryFilter.groupId=false"), + "Message should contain the groupId workaround property"); + assertTrue( + summary.getMessage().contains("remote repository filter"), + "Message should explain the filtering 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");