You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by cs...@apache.org on 2026/07/24 12:02:03 UTC

(maven-resolver) branch master updated: Enh: Allow validator factory to abstain (#2008)

This is an automated email from the ASF dual-hosted git repository.

cstamas pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven-resolver.git


The following commit(s) were added to refs/heads/master by this push:
     new f164aa904 Enh: Allow validator factory to abstain (#2008)
f164aa904 is described below

commit f164aa904ade545c015bbb2d880ebac0625a04b0
Author: Tamas Cservenak <ta...@cservenak.net>
AuthorDate: Fri Jul 24 14:01:58 2026 +0200

    Enh: Allow validator factory to abstain (#2008)
    
    Changes:
    * allow `ValidatorFactory` to "abstain" and return `NOOP`
    * introduce alt execution path for managed dependencies (use cases varies)
    * cache validators per session
    
    Fixes: #2007
---
 .../impl/DefaultRepositorySystemValidator.java     | 289 ++++++++++++---------
 .../DefaultRepositorySystemReentrancyTest.java     |   6 +-
 .../internal/impl/DefaultRepositorySystemTest.java |   2 +-
 .../impl/DefaultRepositorySystemValidatorTest.java | 157 +++++++++++
 .../eclipse/aether/spi/validator/Validator.java    |  26 +-
 .../aether/spi/validator/ValidatorFactory.java     |  16 +-
 .../aether/supplier/RepositorySystemSupplier.java  |  10 +-
 .../aether/supplier/RepositorySystemSupplier.java  |  10 +-
 8 files changed, 364 insertions(+), 152 deletions(-)

diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystemValidator.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystemValidator.java
index 15c85f20d..0d99e88ba 100644
--- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystemValidator.java
+++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystemValidator.java
@@ -25,7 +25,10 @@ import javax.inject.Singleton;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 
+import org.eclipse.aether.Keys;
 import org.eclipse.aether.RepositorySystemSession;
 import org.eclipse.aether.artifact.Artifact;
 import org.eclipse.aether.collection.CollectRequest;
@@ -50,10 +53,11 @@ import static java.util.Objects.requireNonNull;
 @Singleton
 @Named
 public class DefaultRepositorySystemValidator implements RepositorySystemValidator {
-    private final List<ValidatorFactory> validatorFactories;
+    private static final Object SESSION_VALIDATORS = Keys.of(DefaultRepositorySystemValidator.class, "validators");
+    private final Map<String, ValidatorFactory> validatorFactories;
 
     @Inject
-    public DefaultRepositorySystemValidator(List<ValidatorFactory> validatorFactories) {
+    public DefaultRepositorySystemValidator(Map<String, ValidatorFactory> validatorFactories) {
         this.validatorFactories = requireNonNull(validatorFactories, "validatorFactories cannot be null");
     }
 
@@ -65,22 +69,35 @@ public class DefaultRepositorySystemValidator implements RepositorySystemValidat
         }
     }
 
+    @SuppressWarnings("unchecked")
+    private Validator newInstance(RepositorySystemSession session, String name, ValidatorFactory factory) {
+        if (session.getCache() != null) {
+            return ((ConcurrentHashMap<String, Validator>)
+                            session.getCache().computeIfAbsent(session, SESSION_VALIDATORS, ConcurrentHashMap::new))
+                    .computeIfAbsent(name, k -> requireNonNull(factory.newInstance(session)));
+        } else {
+            return requireNonNull(factory.newInstance(session));
+        }
+    }
+
     @Override
     public void validateVersionRequest(RepositorySystemSession session, VersionRequest request) {
         ArrayList<Exception> exceptions = new ArrayList<>();
-        for (ValidatorFactory factory : validatorFactories) {
-            Validator validator = factory.newInstance(session);
-            try {
-                validator.validateArtifact(request.getArtifact());
-            } catch (Exception e) {
-                exceptions.add(e);
-            }
-            for (RemoteRepository repository : request.getRepositories()) {
+        for (Map.Entry<String, ValidatorFactory> entry : validatorFactories.entrySet()) {
+            Validator validator = newInstance(session, entry.getKey(), entry.getValue());
+            if (validator != ValidatorFactory.NOOP) {
                 try {
-                    validator.validateRemoteRepository(repository);
+                    validator.validateArtifact(request.getArtifact());
                 } catch (Exception e) {
                     exceptions.add(e);
                 }
+                for (RemoteRepository repository : request.getRepositories()) {
+                    try {
+                        validator.validateRemoteRepository(repository);
+                    } catch (Exception e) {
+                        exceptions.add(e);
+                    }
+                }
             }
         }
         mayThrow(exceptions, "Invalid Version Request: " + request);
@@ -89,19 +106,21 @@ public class DefaultRepositorySystemValidator implements RepositorySystemValidat
     @Override
     public void validateVersionRangeRequest(RepositorySystemSession session, VersionRangeRequest request) {
         ArrayList<Exception> exceptions = new ArrayList<>();
-        for (ValidatorFactory factory : validatorFactories) {
-            Validator validator = factory.newInstance(session);
-            try {
-                validator.validateArtifact(request.getArtifact());
-            } catch (Exception e) {
-                exceptions.add(e);
-            }
-            for (RemoteRepository repository : request.getRepositories()) {
+        for (Map.Entry<String, ValidatorFactory> entry : validatorFactories.entrySet()) {
+            Validator validator = newInstance(session, entry.getKey(), entry.getValue());
+            if (validator != ValidatorFactory.NOOP) {
                 try {
-                    validator.validateRemoteRepository(repository);
+                    validator.validateArtifact(request.getArtifact());
                 } catch (Exception e) {
                     exceptions.add(e);
                 }
+                for (RemoteRepository repository : request.getRepositories()) {
+                    try {
+                        validator.validateRemoteRepository(repository);
+                    } catch (Exception e) {
+                        exceptions.add(e);
+                    }
+                }
             }
         }
         mayThrow(exceptions, "Invalid Version Range Request: " + request);
@@ -110,19 +129,21 @@ public class DefaultRepositorySystemValidator implements RepositorySystemValidat
     @Override
     public void validateArtifactDescriptorRequest(RepositorySystemSession session, ArtifactDescriptorRequest request) {
         ArrayList<Exception> exceptions = new ArrayList<>();
-        for (ValidatorFactory factory : validatorFactories) {
-            Validator validator = factory.newInstance(session);
-            try {
-                validator.validateArtifact(request.getArtifact());
-            } catch (Exception e) {
-                exceptions.add(e);
-            }
-            for (RemoteRepository repository : request.getRepositories()) {
+        for (Map.Entry<String, ValidatorFactory> entry : validatorFactories.entrySet()) {
+            Validator validator = newInstance(session, entry.getKey(), entry.getValue());
+            if (validator != ValidatorFactory.NOOP) {
                 try {
-                    validator.validateRemoteRepository(repository);
+                    validator.validateArtifact(request.getArtifact());
                 } catch (Exception e) {
                     exceptions.add(e);
                 }
+                for (RemoteRepository repository : request.getRepositories()) {
+                    try {
+                        validator.validateRemoteRepository(repository);
+                    } catch (Exception e) {
+                        exceptions.add(e);
+                    }
+                }
             }
         }
         mayThrow(exceptions, "Invalid Artifact Descriptor Request: " + request);
@@ -132,20 +153,22 @@ public class DefaultRepositorySystemValidator implements RepositorySystemValidat
     public void validateArtifactRequests(
             RepositorySystemSession session, Collection<? extends ArtifactRequest> requests) {
         ArrayList<Exception> exceptions = new ArrayList<>();
-        for (ValidatorFactory factory : validatorFactories) {
-            Validator validator = factory.newInstance(session);
-            for (ArtifactRequest request : requests) {
-                try {
-                    validator.validateArtifact(request.getArtifact());
-                } catch (Exception e) {
-                    exceptions.add(e);
-                }
-                for (RemoteRepository repository : request.getRepositories()) {
+        for (Map.Entry<String, ValidatorFactory> entry : validatorFactories.entrySet()) {
+            Validator validator = newInstance(session, entry.getKey(), entry.getValue());
+            if (validator != ValidatorFactory.NOOP) {
+                for (ArtifactRequest request : requests) {
                     try {
-                        validator.validateRemoteRepository(repository);
+                        validator.validateArtifact(request.getArtifact());
                     } catch (Exception e) {
                         exceptions.add(e);
                     }
+                    for (RemoteRepository repository : request.getRepositories()) {
+                        try {
+                            validator.validateRemoteRepository(repository);
+                        } catch (Exception e) {
+                            exceptions.add(e);
+                        }
+                    }
                 }
             }
         }
@@ -156,20 +179,22 @@ public class DefaultRepositorySystemValidator implements RepositorySystemValidat
     public void validateMetadataRequests(
             RepositorySystemSession session, Collection<? extends MetadataRequest> requests) {
         ArrayList<Exception> exceptions = new ArrayList<>();
-        for (ValidatorFactory factory : validatorFactories) {
-            Validator validator = factory.newInstance(session);
-            for (MetadataRequest request : requests) {
-                try {
-                    validator.validateMetadata(request.getMetadata());
-                } catch (Exception e) {
-                    exceptions.add(e);
-                }
-                try {
-                    if (request.getRepository() != null) {
-                        validator.validateRemoteRepository(request.getRepository());
+        for (Map.Entry<String, ValidatorFactory> entry : validatorFactories.entrySet()) {
+            Validator validator = newInstance(session, entry.getKey(), entry.getValue());
+            if (validator != ValidatorFactory.NOOP) {
+                for (MetadataRequest request : requests) {
+                    try {
+                        validator.validateMetadata(request.getMetadata());
+                    } catch (Exception e) {
+                        exceptions.add(e);
+                    }
+                    try {
+                        if (request.getRepository() != null) {
+                            validator.validateRemoteRepository(request.getRepository());
+                        }
+                    } catch (Exception e) {
+                        exceptions.add(e);
                     }
-                } catch (Exception e) {
-                    exceptions.add(e);
                 }
             }
         }
@@ -179,41 +204,43 @@ public class DefaultRepositorySystemValidator implements RepositorySystemValidat
     @Override
     public void validateCollectRequest(RepositorySystemSession session, CollectRequest request) {
         ArrayList<Exception> exceptions = new ArrayList<>();
-        for (ValidatorFactory factory : validatorFactories) {
-            Validator validator = factory.newInstance(session);
-            if (request.getRootArtifact() != null) {
-                try {
-                    validator.validateArtifact(request.getRootArtifact());
-                } catch (Exception e) {
-                    exceptions.add(e);
+        for (Map.Entry<String, ValidatorFactory> entry : validatorFactories.entrySet()) {
+            Validator validator = newInstance(session, entry.getKey(), entry.getValue());
+            if (validator != ValidatorFactory.NOOP) {
+                if (request.getRootArtifact() != null) {
+                    try {
+                        validator.validateArtifact(request.getRootArtifact());
+                    } catch (Exception e) {
+                        exceptions.add(e);
+                    }
                 }
-            }
-            if (request.getRoot() != null) {
-                try {
-                    validator.validateDependency(request.getRoot());
-                } catch (Exception e) {
-                    exceptions.add(e);
+                if (request.getRoot() != null) {
+                    try {
+                        validator.validateDependency(request.getRoot());
+                    } catch (Exception e) {
+                        exceptions.add(e);
+                    }
                 }
-            }
-            for (Dependency dependency : request.getDependencies()) {
-                try {
-                    validator.validateDependency(dependency);
-                } catch (Exception e) {
-                    exceptions.add(e);
+                for (Dependency dependency : request.getDependencies()) {
+                    try {
+                        validator.validateDependency(dependency);
+                    } catch (Exception e) {
+                        exceptions.add(e);
+                    }
                 }
-            }
-            for (Dependency managedDependency : request.getManagedDependencies()) {
-                try {
-                    validator.validateDependency(managedDependency);
-                } catch (Exception e) {
-                    exceptions.add(e);
+                for (Dependency managedDependency : request.getManagedDependencies()) {
+                    try {
+                        validator.validateManagedDependency(managedDependency);
+                    } catch (Exception e) {
+                        exceptions.add(e);
+                    }
                 }
-            }
-            for (RemoteRepository repository : request.getRepositories()) {
-                try {
-                    validator.validateRemoteRepository(repository);
-                } catch (Exception e) {
-                    exceptions.add(e);
+                for (RemoteRepository repository : request.getRepositories()) {
+                    try {
+                        validator.validateRemoteRepository(repository);
+                    } catch (Exception e) {
+                        exceptions.add(e);
+                    }
                 }
             }
         }
@@ -234,20 +261,22 @@ public class DefaultRepositorySystemValidator implements RepositorySystemValidat
     @Override
     public void validateInstallRequest(RepositorySystemSession session, InstallRequest request) {
         ArrayList<Exception> exceptions = new ArrayList<>();
-        for (ValidatorFactory factory : validatorFactories) {
-            Validator validator = factory.newInstance(session);
-            for (Artifact artifact : request.getArtifacts()) {
-                try {
-                    validator.validateArtifact(artifact);
-                } catch (Exception e) {
-                    exceptions.add(e);
+        for (Map.Entry<String, ValidatorFactory> entry : validatorFactories.entrySet()) {
+            Validator validator = newInstance(session, entry.getKey(), entry.getValue());
+            if (validator != ValidatorFactory.NOOP) {
+                for (Artifact artifact : request.getArtifacts()) {
+                    try {
+                        validator.validateArtifact(artifact);
+                    } catch (Exception e) {
+                        exceptions.add(e);
+                    }
                 }
-            }
-            for (Metadata metadata : request.getMetadata()) {
-                try {
-                    validator.validateMetadata(metadata);
-                } catch (Exception e) {
-                    exceptions.add(e);
+                for (Metadata metadata : request.getMetadata()) {
+                    try {
+                        validator.validateMetadata(metadata);
+                    } catch (Exception e) {
+                        exceptions.add(e);
+                    }
                 }
             }
         }
@@ -257,29 +286,31 @@ public class DefaultRepositorySystemValidator implements RepositorySystemValidat
     @Override
     public void validateDeployRequest(RepositorySystemSession session, DeployRequest request) {
         ArrayList<Exception> exceptions = new ArrayList<>();
-        for (ValidatorFactory factory : validatorFactories) {
-            Validator validator = factory.newInstance(session);
-            for (Artifact artifact : request.getArtifacts()) {
-                try {
-                    validator.validateArtifact(artifact);
-                } catch (Exception e) {
-                    exceptions.add(e);
+        for (Map.Entry<String, ValidatorFactory> entry : validatorFactories.entrySet()) {
+            Validator validator = newInstance(session, entry.getKey(), entry.getValue());
+            if (validator != ValidatorFactory.NOOP) {
+                for (Artifact artifact : request.getArtifacts()) {
+                    try {
+                        validator.validateArtifact(artifact);
+                    } catch (Exception e) {
+                        exceptions.add(e);
+                    }
+                }
+                for (Metadata metadata : request.getMetadata()) {
+                    try {
+                        validator.validateMetadata(metadata);
+                    } catch (Exception e) {
+                        exceptions.add(e);
+                    }
                 }
-            }
-            for (Metadata metadata : request.getMetadata()) {
                 try {
-                    validator.validateMetadata(metadata);
+                    if (request.getRepository() != null) {
+                        validator.validateRemoteRepository(request.getRepository());
+                    }
                 } catch (Exception e) {
                     exceptions.add(e);
                 }
             }
-            try {
-                if (request.getRepository() != null) {
-                    validator.validateRemoteRepository(request.getRepository());
-                }
-            } catch (Exception e) {
-                exceptions.add(e);
-            }
         }
         mayThrow(exceptions, "Invalid Deploy Request: " + request);
     }
@@ -287,13 +318,15 @@ public class DefaultRepositorySystemValidator implements RepositorySystemValidat
     @Override
     public void validateLocalRepositories(RepositorySystemSession session, Collection<LocalRepository> repositories) {
         ArrayList<Exception> exceptions = new ArrayList<>();
-        for (ValidatorFactory factory : validatorFactories) {
-            Validator validator = factory.newInstance(session);
-            for (LocalRepository repository : repositories) {
-                try {
-                    validator.validateLocalRepository(repository);
-                } catch (Exception e) {
-                    exceptions.add(e);
+        for (Map.Entry<String, ValidatorFactory> entry : validatorFactories.entrySet()) {
+            Validator validator = newInstance(session, entry.getKey(), entry.getValue());
+            if (validator != ValidatorFactory.NOOP) {
+                for (LocalRepository repository : repositories) {
+                    try {
+                        validator.validateLocalRepository(repository);
+                    } catch (Exception e) {
+                        exceptions.add(e);
+                    }
                 }
             }
         }
@@ -303,13 +336,15 @@ public class DefaultRepositorySystemValidator implements RepositorySystemValidat
     @Override
     public void validateRemoteRepositories(RepositorySystemSession session, Collection<RemoteRepository> repositories) {
         ArrayList<Exception> exceptions = new ArrayList<>();
-        for (ValidatorFactory factory : validatorFactories) {
-            Validator validator = factory.newInstance(session);
-            for (RemoteRepository repository : repositories) {
-                try {
-                    validator.validateRemoteRepository(repository);
-                } catch (Exception e) {
-                    exceptions.add(e);
+        for (Map.Entry<String, ValidatorFactory> entry : validatorFactories.entrySet()) {
+            Validator validator = newInstance(session, entry.getKey(), entry.getValue());
+            if (validator != ValidatorFactory.NOOP) {
+                for (RemoteRepository repository : repositories) {
+                    try {
+                        validator.validateRemoteRepository(repository);
+                    } catch (Exception e) {
+                        exceptions.add(e);
+                    }
                 }
             }
         }
diff --git a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultRepositorySystemReentrancyTest.java b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultRepositorySystemReentrancyTest.java
index 6aa9e10de..bfb6d356b 100644
--- a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultRepositorySystemReentrancyTest.java
+++ b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultRepositorySystemReentrancyTest.java
@@ -100,7 +100,7 @@ public class DefaultRepositorySystemReentrancyTest {
                         new DefaultRepositoryKeyFunctionFactory()),
                 new DefaultRepositorySystemLifecycle(),
                 Collections.emptyMap(),
-                new DefaultRepositorySystemValidator(Collections.singletonList(countingValidator)));
+                new DefaultRepositorySystemValidator(Collections.singletonMap("counting", countingValidator)));
         session = TestUtils.newSession();
     }
 
@@ -162,7 +162,7 @@ public class DefaultRepositorySystemReentrancyTest {
                 new DefaultRepositorySystemLifecycle(),
                 Collections.emptyMap(),
                 new DefaultRepositorySystemValidator(
-                        Collections.singletonList(EXPRESSION_REJECTING_VALIDATOR_FACTORY)));
+                        Collections.singletonMap("expressionRejecting", EXPRESSION_REJECTING_VALIDATOR_FACTORY)));
 
         // An outermost call with an uninterpolated expression MUST fail validation
         VersionRangeRequest outerBadRequest =
@@ -221,7 +221,7 @@ public class DefaultRepositorySystemReentrancyTest {
                         new DefaultRepositoryKeyFunctionFactory()),
                 new DefaultRepositorySystemLifecycle(),
                 Collections.singletonMap("test", decoratorFactory),
-                new DefaultRepositorySystemValidator(Collections.emptyList()));
+                new DefaultRepositorySystemValidator(Collections.emptyMap()));
 
         // Outermost call: decorator should run
         ArtifactDescriptorRequest outerRequest =
diff --git a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultRepositorySystemTest.java b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultRepositorySystemTest.java
index 4fa1953d5..ff033e0b6 100644
--- a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultRepositorySystemTest.java
+++ b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultRepositorySystemTest.java
@@ -69,7 +69,7 @@ public class DefaultRepositorySystemTest {
                         new DefaultRepositoryKeyFunctionFactory()),
                 new DefaultRepositorySystemLifecycle(),
                 Collections.emptyMap(),
-                new DefaultRepositorySystemValidator(Collections.emptyList()));
+                new DefaultRepositorySystemValidator(Collections.emptyMap()));
         session = TestUtils.newSession();
     }
 
diff --git a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultRepositorySystemValidatorTest.java b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultRepositorySystemValidatorTest.java
new file mode 100644
index 000000000..bd8d0ddfb
--- /dev/null
+++ b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultRepositorySystemValidatorTest.java
@@ -0,0 +1,157 @@
+/*
+ * 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.eclipse.aether.internal.impl;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.aether.DefaultRepositoryCache;
+import org.eclipse.aether.DefaultRepositorySystemSession;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.artifact.DefaultArtifact;
+import org.eclipse.aether.collection.CollectRequest;
+import org.eclipse.aether.graph.Dependency;
+import org.eclipse.aether.internal.test.util.TestUtils;
+import org.eclipse.aether.resolution.ArtifactDescriptorRequest;
+import org.eclipse.aether.spi.validator.Validator;
+import org.eclipse.aether.spi.validator.ValidatorFactory;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class DefaultRepositorySystemValidatorTest {
+    @Test
+    void abstain() {
+        DefaultRepositorySystemValidator validator = new DefaultRepositorySystemValidator(
+                Collections.singletonMap("abstain", session -> ValidatorFactory.NOOP));
+        ArtifactDescriptorRequest request =
+                new ArtifactDescriptorRequest(new DefaultArtifact("foo:bar:1.0"), Collections.emptyList(), "test");
+        assertDoesNotThrow(() -> validator.validateArtifactDescriptorRequest(TestUtils.newSession(), request));
+    }
+
+    @Test
+    void fail() {
+        DefaultRepositorySystemValidator validator =
+                new DefaultRepositorySystemValidator(Collections.singletonMap("fail", session -> new Validator() {
+                    @Override
+                    public void validateArtifact(Artifact artifact) throws IllegalArgumentException {
+                        throw new IllegalArgumentException("Artifact validation failed");
+                    }
+                }));
+        ArtifactDescriptorRequest request =
+                new ArtifactDescriptorRequest(new DefaultArtifact("foo:bar:1.0"), Collections.emptyList(), "test");
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> validator.validateArtifactDescriptorRequest(TestUtils.newSession(), request));
+    }
+
+    @Test
+    void withCaching() {
+        final ConcurrentHashMap<Integer, AtomicInteger> instanceCounter = new ConcurrentHashMap<>();
+        DefaultRepositorySystemValidator validator =
+                new DefaultRepositorySystemValidator(Collections.singletonMap("counter", session -> new Validator() {
+                    @Override
+                    public void validateArtifact(Artifact artifact) throws IllegalArgumentException {
+                        instanceCounter
+                                .computeIfAbsent(System.identityHashCode(this), k -> new AtomicInteger(0))
+                                .incrementAndGet();
+                    }
+                }));
+        DefaultRepositorySystemSession sameSession = TestUtils.newSession();
+        sameSession.setCache(new DefaultRepositoryCache()); // with cache
+        assertDoesNotThrow(() -> validator.validateArtifactDescriptorRequest(
+                sameSession,
+                new ArtifactDescriptorRequest(new DefaultArtifact("foo:bar:1.0"), Collections.emptyList(), "test")));
+        assertDoesNotThrow(() -> validator.validateArtifactDescriptorRequest(
+                sameSession,
+                new ArtifactDescriptorRequest(new DefaultArtifact("baz:foo:1.0"), Collections.emptyList(), "test")));
+        // one instance is
+        assertEquals(1, instanceCounter.size());
+        // used twice
+        assertEquals(
+                2,
+                instanceCounter
+                        .get(instanceCounter.keySet().stream().iterator().next())
+                        .get());
+    }
+
+    @Test
+    void withoutCaching() {
+        final ConcurrentHashMap<Integer, AtomicInteger> instanceCounter = new ConcurrentHashMap<>();
+        DefaultRepositorySystemValidator validator =
+                new DefaultRepositorySystemValidator(Collections.singletonMap("counter", session -> new Validator() {
+                    @Override
+                    public void validateArtifact(Artifact artifact) throws IllegalArgumentException {
+                        instanceCounter
+                                .computeIfAbsent(System.identityHashCode(this), k -> new AtomicInteger(0))
+                                .incrementAndGet();
+                    }
+                }));
+        DefaultRepositorySystemSession sameSession = TestUtils.newSession();
+        sameSession.setCache(null); // without cache
+        assertDoesNotThrow(() -> validator.validateArtifactDescriptorRequest(
+                sameSession,
+                new ArtifactDescriptorRequest(new DefaultArtifact("foo:bar:1.0"), Collections.emptyList(), "test")));
+        assertDoesNotThrow(() -> validator.validateArtifactDescriptorRequest(
+                sameSession,
+                new ArtifactDescriptorRequest(new DefaultArtifact("baz:foo:1.0"), Collections.emptyList(), "test")));
+        // two instances
+        assertEquals(2, instanceCounter.size());
+        for (AtomicInteger counter : instanceCounter.values()) {
+            // each used once
+            assertEquals(1, counter.get());
+        }
+    }
+
+    @Test
+    void dependencies() {
+        final List<Dependency> dependencies =
+                Collections.singletonList(new Dependency(new DefaultArtifact("foo:bar:1.0"), "compile"));
+        final List<Dependency> managedDependencies =
+                Collections.singletonList(new Dependency(new DefaultArtifact("baz:foo:1.0"), "compile"));
+
+        final AtomicInteger validateDependencies = new AtomicInteger(0);
+        final AtomicInteger validateManagedDependencies = new AtomicInteger(0);
+        DefaultRepositorySystemValidator validator = new DefaultRepositorySystemValidator(Collections.singletonMap(
+                "dependencies", session -> new Validator() {
+                    @Override
+                    public void validateDependency(Dependency dependency) throws IllegalArgumentException {
+                        assertTrue(dependencies.contains(dependency));
+                        validateDependencies.incrementAndGet();
+                    }
+
+                    @Override
+                    public void validateManagedDependency(Dependency managedDependency)
+                            throws IllegalArgumentException {
+                        assertTrue(managedDependencies.contains(managedDependency));
+                        validateManagedDependencies.incrementAndGet();
+                    }
+                }));
+        assertDoesNotThrow(() -> validator.validateCollectRequest(
+                TestUtils.newSession(),
+                new CollectRequest(dependencies, managedDependencies, Collections.emptyList())));
+        assertEquals(1, validateDependencies.get());
+        assertEquals(1, validateManagedDependencies.get());
+    }
+}
diff --git a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/validator/Validator.java b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/validator/Validator.java
index b41757609..16778c934 100644
--- a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/validator/Validator.java
+++ b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/validator/Validator.java
@@ -33,7 +33,7 @@ public interface Validator {
     /**
      * Validates artifact.
      *
-     * @param artifact The artifact to validate, never {@code null}.
+     * @param artifact the artifact to validate, never {@code null}.
      * @throws IllegalArgumentException if artifact is invalid.
      */
     default void validateArtifact(Artifact artifact) throws IllegalArgumentException {}
@@ -41,23 +41,37 @@ public interface Validator {
     /**
      * Validates metadata.
      *
-     * @param metadata The metadata to validate, never {@code null}.
-     * @throws IllegalArgumentException if artifact is invalid.
+     * @param metadata the metadata to validate, never {@code null}.
+     * @throws IllegalArgumentException if metadata is invalid.
      */
     default void validateMetadata(Metadata metadata) throws IllegalArgumentException {}
 
     /**
      * Validates dependency.
      *
-     * @param dependency The dependency to validate, never {@code null}.
+     * @param dependency the dependency to validate, never {@code null}.
      * @throws IllegalArgumentException if dependency is invalid.
      */
     default void validateDependency(Dependency dependency) throws IllegalArgumentException {}
 
+    /**
+     * Validates managed dependency.
+     * <em>Important:</em> They are declarative constraints (version/scope/exclusion overrides) that only take effect
+     * when a matching dependency is encountered during collection. Validating them eagerly may reject valid builds
+     * where a BOM imports managed dependencies with uninterpolated property expressions (e.g. {@code ${osgi.version}})
+     * that are never actually used. If a managed dependency IS matched and its coordinates are invalid, the error
+     * will surface naturally during version resolution or artifact resolution.
+     *
+     * @param managedDependency the managed dependency to validate, never {@code null}.
+     * @throws IllegalArgumentException if dependency is invalid.
+     * @since 2.0.22
+     */
+    default void validateManagedDependency(Dependency managedDependency) throws IllegalArgumentException {}
+
     /**
      * Validates local repository.
      *
-     * @param localRepository The local repository to validate, never {@code null}.
+     * @param localRepository the local repository to validate, never {@code null}.
      * @throws IllegalArgumentException if local repository is invalid.
      */
     default void validateLocalRepository(LocalRepository localRepository) throws IllegalArgumentException {}
@@ -65,7 +79,7 @@ public interface Validator {
     /**
      * Validates remote repository.
      *
-     * @param remoteRepository The remote repository to validate, never {@code null}.
+     * @param remoteRepository the remote repository to validate, never {@code null}.
      * @throws IllegalArgumentException if remote repository is invalid.
      */
     default void validateRemoteRepository(RemoteRepository remoteRepository) throws IllegalArgumentException {}
diff --git a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/validator/ValidatorFactory.java b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/validator/ValidatorFactory.java
index 23e59a8f6..7221d40e9 100644
--- a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/validator/ValidatorFactory.java
+++ b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/validator/ValidatorFactory.java
@@ -26,11 +26,21 @@ import org.eclipse.aether.RepositorySystemSession;
  * @since 2.0.8
  */
 public interface ValidatorFactory {
+
+    /**
+     * The no-op (does not validate anything) instance of validator.
+     *
+     * @since 2.0.22
+     */
+    Validator NOOP = new Validator() {};
+
     /**
-     * Creates a new validator for the session.
+     * Creates a new validator for the session, or {@link #NOOP} to abstain from validation. Factory is consulted
+     * once per session (if cache present in session) and returned instances are cached and reused
+     * across single session.
      *
-     * @param session The repository system session from which to configure the validator, must not be {@code null}.
-     * @return The validator for the session, never {@code null}.
+     * @param session the repository system session from which to configure the validator, must not be {@code null}.
+     * @return the validator to be used for the session, or {@link #NOOP} to abstain from validation, never {@code null}.
      */
     Validator newInstance(RepositorySystemSession session);
 }
diff --git a/maven-resolver-supplier-mvn3/src/main/java/org/eclipse/aether/supplier/RepositorySystemSupplier.java b/maven-resolver-supplier-mvn3/src/main/java/org/eclipse/aether/supplier/RepositorySystemSupplier.java
index bff9ca9a1..4a7b70f14 100644
--- a/maven-resolver-supplier-mvn3/src/main/java/org/eclipse/aether/supplier/RepositorySystemSupplier.java
+++ b/maven-resolver-supplier-mvn3/src/main/java/org/eclipse/aether/supplier/RepositorySystemSupplier.java
@@ -18,9 +18,7 @@
  */
 package org.eclipse.aether.supplier;
 
-import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Supplier;
@@ -1131,9 +1129,9 @@ public class RepositorySystemSupplier implements Supplier<RepositorySystem> {
         return new DefaultModelCacheFactory();
     }
 
-    private List<ValidatorFactory> validatorFactories;
+    private Map<String, ValidatorFactory> validatorFactories;
 
-    public final List<ValidatorFactory> getValidatorFactories() {
+    public final Map<String, ValidatorFactory> getValidatorFactories() {
         checkClosed();
         if (validatorFactories == null) {
             validatorFactories = createValidatorFactories();
@@ -1141,8 +1139,8 @@ public class RepositorySystemSupplier implements Supplier<RepositorySystem> {
         return validatorFactories;
     }
 
-    protected List<ValidatorFactory> createValidatorFactories() {
-        return new ArrayList<>();
+    protected Map<String, ValidatorFactory> createValidatorFactories() {
+        return new HashMap<>();
     }
 
     private RepositorySystemValidator repositorySystemValidator;
diff --git a/maven-resolver-supplier-mvn4/src/main/java/org/eclipse/aether/supplier/RepositorySystemSupplier.java b/maven-resolver-supplier-mvn4/src/main/java/org/eclipse/aether/supplier/RepositorySystemSupplier.java
index 3c78121fe..637b21e5a 100644
--- a/maven-resolver-supplier-mvn4/src/main/java/org/eclipse/aether/supplier/RepositorySystemSupplier.java
+++ b/maven-resolver-supplier-mvn4/src/main/java/org/eclipse/aether/supplier/RepositorySystemSupplier.java
@@ -18,10 +18,8 @@
  */
 package org.eclipse.aether.supplier;
 
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
-import java.util.List;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Supplier;
@@ -1156,9 +1154,9 @@ public class RepositorySystemSupplier implements Supplier<RepositorySystem> {
         return new DefaultModelCacheFactory();
     }
 
-    private List<ValidatorFactory> validatorFactories;
+    private Map<String, ValidatorFactory> validatorFactories;
 
-    public final List<ValidatorFactory> getValidatorFactories() {
+    public final Map<String, ValidatorFactory> getValidatorFactories() {
         checkClosed();
         if (validatorFactories == null) {
             validatorFactories = createValidatorFactories();
@@ -1166,8 +1164,8 @@ public class RepositorySystemSupplier implements Supplier<RepositorySystem> {
         return validatorFactories;
     }
 
-    protected List<ValidatorFactory> createValidatorFactories() {
-        return new ArrayList<>();
+    protected Map<String, ValidatorFactory> createValidatorFactories() {
+        return new HashMap<>();
     }
 
     private RepositorySystemValidator repositorySystemValidator;