junit-team/junit5 · critical · JUnitException

Third-party TestEngine '%s' is forbidden to use the reserved

Error message

Third-party TestEngine '%s' is forbidden to use the reserved '%s' TestEngine ID.

What it means

Thrown by EngineIdValidator.validateWellKnownClassName() when a TestEngine whose class is NOT one of JUnit's well-known implementations claims a reserved engine ID ('junit-jupiter', 'junit-vintage', or 'junit-platform-suite'). This guards against third-party engines impersonating official engines (issue #1557). The exception names the offending class and the reserved ID.

Source

Thrown at junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineIdValidator.java:82

	}

	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()));
		return switch (engineId) {
			case "junit-jupiter" -> "org.junit.jupiter.engine.JupiterTestEngine";
			case "junit-vintage" -> "org.junit.vintage.engine.VintageTestEngine";
			case "junit-platform-suite" -> "org.junit.platform.suite.engine.SuiteTestEngine";
			default -> null;
		};
	}

	private static void validateWellKnownClassName(TestEngine testEngine, String expectedClassName) {
		String actualClassName = testEngine.getClass().getName();
		if (actualClassName.equals(expectedClassName)) {
			return;
		}
		throw new JUnitException(
			"Third-party TestEngine '%s' is forbidden to use the reserved '%s' TestEngine ID.".formatted(
				actualClassName, testEngine.getId()));
	}

}

View on GitHub (pinned to 956246301e)

Solutions

  1. Give your custom TestEngine a unique, non-reserved ID (do not start with 'junit-' and do not reuse official IDs).
  2. If you relocated the official engine via shading, also relocate its class so the well-known class name still matches, or change the ID.
  3. Remove the offending custom TestEngine from the ServiceLoader registrations.

Example fix

// before
public class MyEngine implements TestEngine {
    public String getId() { return "junit-jupiter"; } // forbidden
}

// after
public class MyEngine implements TestEngine {
    public String getId() { return "my-company-engine"; }
}
Defensive patterns

Strategy: validation

Validate before calling

import org.junit.platform.engine.TestEngine;
Set<String> RESERVED = Set.of("junit-jupiter","junit-vintage","junit-platform-suite");
for (var eng : ServiceLoader.load(TestEngine.class)) {
    if (RESERVED.contains(eng.getId()) && !isWellKnown(eng)) {
        throw new IllegalStateException("Custom engine must not use reserved id: " + eng.getId());
    }
}

Type guard

static boolean usesReservedId(TestEngine e) {
    return e.getId().startsWith("junit-") || Set.of(
        "junit-jupiter","junit-vintage","junit-platform-suite").contains(e.getId());
}

Prevention

When it happens

Trigger: A custom TestEngine whose getId() returns 'junit-jupiter' but whose class is not org.junit.jupiter.engine.JupiterTestEngine (similarly for vintage/suite). Triggered at Launcher creation via EngineIdValidator.validate().

Common situations: Writing a custom/forked engine and reusing an official ID. Bytecode relocation/shading that moves JupiterTestEngine to a new package but keeps getId()='junit-jupiter'. A malicious or mistaken ServiceLoader provider impersonating an official engine.

Related errors


AI-assisted analysis of junit-team/junit5@956246301e (2026-08-04). Data as JSON: /data/errors/32c794f2108becda.json. Report an issue: GitHub.