junit-team/junit5 · error · JUnitException
Cannot create Launcher for multiple engines with the same ID
Error message
Cannot create Launcher for multiple engines with the same ID '%s'.
What it means
EngineIdValidator.validate throws if two registered TestEngines report the same getId(). The launcher cannot disambiguate engines by id during discovery and execution, so duplicate ids are a hard error at launcher creation. The id is reported in the message.
Solutions
- Inspect the classpath for duplicate engine artifacts (gradle dependencies / mvn dependency:tree) and exclude the duplicate.
- If authoring a custom TestEngine, choose a unique id (not 'junit-jupiter', 'junit-vintage', 'junit-platform-suite').
- When building a fat/shaded jar, use services resource merging so the same engine provider is not registered twice.
Example fix
// before - duplicate engine on classpath (gradle)
testImplementation('org.junit.jupiter:junit-jupiter-engine')
testImplementation('org.junit.platform:junit-platform-console-standalone') // also bundles it
// after - keep only one source
testImplementation('org.junit.jupiter:junit-jupiter')
testRuntimeOnly('org.junit.platform:junit-platform-launcher') Defensive patterns
Strategy: validation
Validate before calling
// At build time, detect duplicate engine providers
Set<String> ids = new HashSet<>();
for (TestEngine e : ServiceLoader.load(TestEngine.class)) {
if (!ids.add(e.getId())) throw new IllegalStateException("duplicate engine id: " + e.getId());
} Type guard
static boolean allEngineIdsUnique(Iterable<TestEngine> engines) {
Set<String> seen = new HashSet<>();
for (TestEngine e : engines) if (!seen.add(e.getId())) return false;
return true;
} Try / catch
try {
LauncherFactory.create();
} catch (JUnitException e) {
if (e.getMessage().contains("multiple engines with the same ID")) {
// dedupe classpath, then recreate
}
} Prevention
- Run dependency analysis to avoid duplicate engine jars.
- Configure shaded-jar plugins to merge ServiceLoader resources.
- Use unique ids for custom TestEngines.
When it happens
Trigger: Two TestEngine ServiceLoader providers (or manually registered engines) return the same string from TestEngine.getId(). For example, two copies of junit-jupiter-engine on the classpath, or a custom engine whose id collides with a built-in.
Common situations: Duplicate dependency: junit-jupiter-engine present both as a transitive and an explicit dependency, or a shaded/fat jar bundling it twice; a custom engine hardcoded to id 'junit-jupiter' or another taken id; running with both the standalone console (which bundles engines) and explicit engine jars on the classpath.
Related errors
- There should not be more than one ClasspathScanner…
- Could not add entry to the queue for task
- Could not find any resource(s) with name
- Invalid classpath entry
- No TestEngine ID matched the following include EngineFilters
AI-assisted analysis of junit-team/junit5@f070c699a0 (2026-08-11).
Data as JSON: /api/errors/705f6cc7e9ec1d8b.
Report an issue: GitHub.
Appendix: source
Thrown at junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineIdValidator.java:50
}
static void validateReservedPrefix(TestEngine testEngine, UniqueId uniqueEngineId,
DiscoveryIssueCollector issueCollector) {
String engineId = testEngine.getId();
if (engineId.startsWith("junit-") && wellKnownClassNameForEngineId(testEngine) == null) {
var message = "Third-party TestEngine implementations are forbidden to use the reserved 'junit-' prefix for their ID";
issueCollector.issueEncountered(uniqueEngineId, DiscoveryIssue.create(WARNING, message));
}
}
static Iterable<TestEngine> validate(Iterable<TestEngine> testEngines) {
Set<String> ids = new HashSet<>();
for (TestEngine testEngine : testEngines) {
// check usage of reserved ids
validateReservedIds(testEngine);
// check uniqueness
if (!ids.add(testEngine.getId())) {
throw new JUnitException(
"Cannot create Launcher for multiple engines with the same ID '%s'.".formatted(testEngine.getId()));
}
}
return testEngines;
}
// https://github.com/junit-team/junit-framework/issues/1557
private static void validateReservedIds(TestEngine testEngine) {
var expectedClassName = wellKnownClassNameForEngineId(testEngine);
if (expectedClassName == null) {
return;
}
validateWellKnownClassName(testEngine, expectedClassName);
}
private static @Nullable String wellKnownClassNameForEngineId(TestEngine testEngine) {
String engineId = Preconditions.notBlank(testEngine.getId(),
() -> "ID for TestEngine [%s] must not be null or blank".formatted(testEngine.getClass().getName()));View on GitHub (pinned to f070c699a0)