{"id":"32c794f2108becda","repo":"junit-team/junit5","slug":"third-party-testengine-s-is-forbidden-to-use-th","errorCode":null,"errorMessage":"Third-party TestEngine '%s' is forbidden to use the reserved '%s' TestEngine ID.","messagePattern":"Third-party TestEngine '(.+?)' is forbidden to use the reserved '(.+?)' TestEngine ID\\.","errorType":"exception","errorClass":"JUnitException","httpStatus":null,"severity":"critical","filePath":"junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineIdValidator.java","lineNumber":82,"sourceCode":"\t}\n\n\tprivate static @Nullable String wellKnownClassNameForEngineId(TestEngine testEngine) {\n\t\tString engineId = Preconditions.notBlank(testEngine.getId(),\n\t\t\t() -> \"ID for TestEngine [%s] must not be null or blank\".formatted(testEngine.getClass().getName()));\n\t\treturn switch (engineId) {\n\t\t\tcase \"junit-jupiter\" -> \"org.junit.jupiter.engine.JupiterTestEngine\";\n\t\t\tcase \"junit-vintage\" -> \"org.junit.vintage.engine.VintageTestEngine\";\n\t\t\tcase \"junit-platform-suite\" -> \"org.junit.platform.suite.engine.SuiteTestEngine\";\n\t\t\tdefault -> null;\n\t\t};\n\t}\n\n\tprivate static void validateWellKnownClassName(TestEngine testEngine, String expectedClassName) {\n\t\tString actualClassName = testEngine.getClass().getName();\n\t\tif (actualClassName.equals(expectedClassName)) {\n\t\t\treturn;\n\t\t}\n\t\tthrow new JUnitException(\n\t\t\t\"Third-party TestEngine '%s' is forbidden to use the reserved '%s' TestEngine ID.\".formatted(\n\t\t\t\tactualClassName, testEngine.getId()));\n\t}\n\n}\n","sourceCodeStart":64,"sourceCodeEnd":88,"githubUrl":"https://github.com/junit-team/junit5/blob/956246301e03d757539f7b76dd5a91f298637563/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineIdValidator.java#L64-L88","documentation":"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.","triggerScenarios":"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().","commonSituations":"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.","solutions":["Give your custom TestEngine a unique, non-reserved ID (do not start with 'junit-' and do not reuse official IDs).","If you relocated the official engine via shading, also relocate its class so the well-known class name still matches, or change the ID.","Remove the offending custom TestEngine from the ServiceLoader registrations."],"exampleFix":"// before\npublic class MyEngine implements TestEngine {\n    public String getId() { return \"junit-jupiter\"; } // forbidden\n}\n\n// after\npublic class MyEngine implements TestEngine {\n    public String getId() { return \"my-company-engine\"; }\n}","handlingStrategy":"validation","validationCode":"import org.junit.platform.engine.TestEngine;\nSet<String> RESERVED = Set.of(\"junit-jupiter\",\"junit-vintage\",\"junit-platform-suite\");\nfor (var eng : ServiceLoader.load(TestEngine.class)) {\n    if (RESERVED.contains(eng.getId()) && !isWellKnown(eng)) {\n        throw new IllegalStateException(\"Custom engine must not use reserved id: \" + eng.getId());\n    }\n}","typeGuard":"static boolean usesReservedId(TestEngine e) {\n    return e.getId().startsWith(\"junit-\") || Set.of(\n        \"junit-jupiter\",\"junit-vintage\",\"junit-platform-suite\").contains(e.getId());\n}","tryCatchPattern":null,"preventionTips":["Never reuse official engine IDs in custom TestEngine implementations.","If you relocate/shade the official engine, keep its class name intact or change the ID.","Audit ServiceLoader registrations for engines claiming junit-* IDs."],"tags":["engine","reserved-id","configuration","junit-platform"],"analyzedSha":"956246301e03d757539f7b76dd5a91f298637563","analyzedAt":"2026-08-04T19:25:17.049Z","schemaVersion":2}