junit-team/junit5 · error · JUnitException
Selector ${selector} did not yield unique test descriptor: $
Error message
Selector ${selector} did not yield unique test descriptor: ${stringRepresentation} What it means
Thrown by EngineDiscoveryRequestResolution.DefaultContext.resolve (line 226-245) when a nested selector resolves to more than one Match. The discovery model expects that resolving a selector within a parent context yields at most one TestDescriptor; multiple matches indicate ambiguity. The exception lists the colliding descriptors so the user can see what overlapped.
Source
Thrown at junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/EngineDiscoveryRequestResolution.java:236
Function<TestDescriptor, Optional<T>> creator) {
if (parent != null) {
return createAndAdd(parent, creator);
}
return resolve(parentSelectorSupplier.get()).flatMap(parent -> createAndAdd(parent, creator));
}
@Override
public Optional<TestDescriptor> resolve(DiscoverySelector selector) {
// @formatter:off
return EngineDiscoveryRequestResolution.this.resolve(selector)
.map(Resolution::getMatches)
.flatMap(matches -> {
if (matches.size() > 1) {
String stringRepresentation = matches.stream()
.map(Match::getTestDescriptor)
.map(Objects::toString)
.collect(joining(", "));
throw new JUnitException(
"Selector " + selector + " did not yield unique test descriptor: " + stringRepresentation);
}
if (matches.size() == 1) {
return Optional.of(getOnlyElement(matches).getTestDescriptor());
}
return Optional.empty();
});
// @formatter:on
}
@SuppressWarnings("unchecked")
private <T extends TestDescriptor> Optional<T> createAndAdd(TestDescriptor parent,
Function<TestDescriptor, Optional<T>> creator) {
Optional<T> child = creator.apply(parent);
if (child.isPresent()) {
UniqueId uniqueId = child.get().getUniqueId();
if (resolvedUniqueIds.containsKey(uniqueId)) {
return Optional.of((T) resolvedUniqueIds.get(uniqueId).getTestDescriptor());View on GitHub (pinned to 956246301e)
Solutions
- If you author a SelectorResolver, ensure resolve(...) returns a Resolution with a single exact match when the engine calls into a context that requires uniqueness (use Match.exact for one descriptor).
- Eliminate duplicate classes/resources on the classpath that cause a single selector to match multiple descriptors.
- Disambiguate the selector: use MethodSelector with explicit parameter types or a UniqueIdSelector instead of a broad ClassSelector/PackageSelector.
- Read the listed descriptors in the message to identify which two elements collide, then fix their UniqueIds or discovery logic.
Example fix
// before: custom resolver returns multiple matches
@Override public Resolution resolve(ClassSelector sel, Context ctx) {
return Resolution.match(Match.exact(d1)).match(Match.exact(d2)); // ambiguous
}
// after: resolve to exactly one descriptor
@Override public Resolution resolve(ClassSelector sel, Context ctx) {
TestDescriptor unique = resolveOne(sel);
return Resolution.match(Match.exact(unique));
} Defensive patterns
Strategy: validation
Validate before calling
// When authoring a SelectorResolver, never return multiple exact matches from a
// context that requires a unique parent. Resolve to exactly one descriptor:
@Override public Resolution resolve(MethodSelector sel, Context ctx) {
Optional<TestDescriptor> one = findSingle(sel);
return one.map(d -> Resolution.match(Match.exact(d))).orElseGet(Resolution::unresolved);
} Try / catch
try {
context.resolve(mySelector);
} catch (JUnitException e) {
// message lists the colliding descriptors; use them to disambiguate
log.error("ambiguous resolution: {}", e.getMessage());
throw e;
} Prevention
- Ensure SelectorResolver implementations return a single exact Match where uniqueness is required.
- Disambiguate with UniqueIdSelector or MethodSelector including parameter types.
- Eliminate duplicate classes on the classpath.
When it happens
Trigger: A SelectorResolver registered by a TestEngine returns a Resolution with multiple exact Match entries for a single selector, and EngineDiscoveryRequestResolution.DefaultContext.resolve() (used by addToParent that needs a unique parent) cannot pick one. Typically happens with custom resolvers or when two test descriptors collide on the same UniqueId/selector.
Common situations: Writing a custom SelectorResolver that over-resolves (returns multiple matches for a method/class selector); duplicate classes on the classpath so a class selector matches twice; overloaded methods where a resolver returns broad matches; an engine bug producing non-unique UniqueIds.
Related errors
- '%s' is not a well-formed UniqueId segment
- Could not find any resource(s) with name: ${this.classpathRe
- Failed to create a java.net.URI from: ${uri}
- Failed to retrieve canonical path for file: ${file}
- Failed to retrieve canonical path for directory: ${directory
AI-assisted analysis of junit-team/junit5@956246301e (2026-08-04).
Data as JSON: /data/errors/6fb798c9debb17c2.json.
Report an issue: GitHub.