{"record":{"id":"4511c82b50a13304","repo":"junit-team/junit5","slug":"constructor-injection-is-not-supported-for-parame","errorCode":null,"errorMessage":"Constructor injection is not supported for @ParameterizedClass classes with @TestInstance(Lifecycle.PER_CLASS)","messagePattern":"Constructor injection is not supported for @ParameterizedClass classes with @TestInstance\\(Lifecycle\\.PER_CLASS\\)","errorType":"exception","errorClass":"PreconditionViolationException","httpStatus":null,"severity":"error","filePath":"junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedClassExtension.java","lineNumber":119,"sourceCode":"\t\t\treturn false;\n\t\t}\n\n\t\tstore.put(DECLARATION_CONTEXT_KEY,\n\t\t\tcreateClassContext(extensionContext, extensionContext.getRequiredTestClass(), annotation.get()));\n\n\t\treturn true;\n\t}\n\n\tprivate static ParameterizedClassContext createClassContext(ExtensionContext extensionContext, Class<?> testClass,\n\t\t\tParameterizedClass annotation) {\n\n\t\tTestInstance.Lifecycle lifecycle = extensionContext.getTestInstanceLifecycle() //\n\t\t\t\t.orElseThrow(() -> new PreconditionViolationException(\"TestInstance.Lifecycle not present\"));\n\n\t\tParameterizedClassContext classContext = new ParameterizedClassContext(testClass, annotation, lifecycle);\n\n\t\tif (lifecycle == PER_CLASS && classContext.getInjectionType() == CONSTRUCTOR) {\n\t\t\tthrow new PreconditionViolationException(\n\t\t\t\t\"Constructor injection is not supported for @ParameterizedClass classes with @TestInstance(Lifecycle.PER_CLASS)\");\n\t\t}\n\n\t\treturn classContext;\n\t}\n\n\tprivate ParameterizedClassContext getDeclarationContext(ExtensionContext extensionContext) {\n\t\treturn requireNonNull(getStore(extensionContext)//\n\t\t\t\t.get(DECLARATION_CONTEXT_KEY, ParameterizedClassContext.class));\n\t}\n\n\tprivate Store getStore(ExtensionContext context) {\n\t\treturn context.getStore(Namespace.create(ParameterizedClassExtension.class, context.getRequiredTestClass()));\n\t}\n\n}\n","sourceCodeStart":101,"sourceCodeEnd":136,"githubUrl":"https://github.com/junit-team/junit5/blob/f070c699a08b5d8393df9afd147af5c5e90bb21b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedClassExtension.java#L101-L136","documentation":"Thrown when a @ParameterizedClass is configured with constructor-based argument injection (i.e., the @ParameterizedClass constructor takes parameters that are supplied by the arguments source) AND the class is annotated with @TestInstance(Lifecycle.PER_CLASS). PER_CLASS means a single test instance is reused across all invocations, but constructor injection with parameterized arguments requires creating a new instance per argument set — these are fundamentally incompatible.","triggerScenarios":"Annotate a class with both @ParameterizedClass and @TestInstance(Lifecycle.PER_CLASS), and have the constructor accept @Parameter-annotated or positional arguments from the source. The createClassContext method detects injectionType == CONSTRUCTOR combined with lifecycle == PER_CLASS and throws.","commonSituations":"Developers migrating from @ParameterizedTest (method-level) to @ParameterizedClass (class-level) who carry over the PER_CLASS lifecycle. Or developers who want to share state across parameterized invocations (hence PER_CLASS) but also want constructor injection for clean parameter binding — these two desires conflict.","solutions":["Switch to @TestInstance(Lifecycle.PER_METHOD) (the default) which creates a new instance per invocation and supports constructor injection","Keep PER_CLASS but switch from constructor injection to field injection using @Parameter-annotated fields","Keep PER_CLASS and constructor injection but remove the parameterized constructor arguments — use @Parameter fields or method parameters instead"],"exampleFix":"// before — incompatible combination\n@ParameterizedClass\n@TestInstance(Lifecycle.PER_CLASS)\nclass MyTest {\n    private final int value;\n    MyTest(@Parameter int value) { this.value = value; }\n}\n\n// after — use field injection with PER_CLASS\n@ParameterizedClass\n@TestInstance(Lifecycle.PER_CLASS)\nclass MyTest {\n    @Parameter int value;\n}\n\n// OR — use constructor injection with PER_METHOD (default)\n@ParameterizedClass\nclass MyTest {\n    private final int value;\n    MyTest(@Parameter int value) { this.value = value; }\n}","handlingStrategy":"validation","validationCode":"// Before annotating a @ParameterizedClass, check for the PER_CLASS + constructor injection conflict\nimport org.junit.jupiter.api.TestInstance;\n\nboolean isPerClass = testClass.isAnnotationPresent(TestInstance.class)\n    && testClass.getAnnotation(TestInstance.class).value() == TestInstance.Lifecycle.PER_CLASS;\nboolean hasParameterizedConstructor = java.util.Arrays.stream(testClass.getDeclaredConstructors())\n    .anyMatch(c -> c.getParameterCount() > 0);\n\nif (isPerClass && hasParameterizedConstructor && testClass.isAnnotationPresent(ParameterizedClass.class)) {\n    throw new IllegalStateException(\n        \"Constructor injection is not supported with @TestInstance(PER_CLASS). Use field injection (@Parameter) or switch to PER_METHOD.\");\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Default to @TestInstance(PER_METHOD) for @ParameterizedClass unless you specifically need shared state","If using PER_CLASS, prefer @Parameter-annotated fields over constructor parameters for argument injection","Document the PER_CLASS + constructor injection incompatibility in your project's testing guidelines"],"tags":["parameterized-class","test-instance","constructor-injection","configuration","junit-params"],"backgroundTag":null,"analyzedSha":"f070c699a08b5d8393df9afd147af5c5e90bb21b","analyzedAt":"2026-08-11T20:31:00.530Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}