junit-team/junit5 · error · PreconditionViolationException

Constructor injection is not supported for…

Error message

Constructor injection is not supported for @ParameterizedClass classes with @TestInstance(Lifecycle.PER_CLASS)

What it means

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.

Solutions

  1. Switch to @TestInstance(Lifecycle.PER_METHOD) (the default) which creates a new instance per invocation and supports constructor injection
  2. Keep PER_CLASS but switch from constructor injection to field injection using @Parameter-annotated fields
  3. Keep PER_CLASS and constructor injection but remove the parameterized constructor arguments — use @Parameter fields or method parameters instead

Example fix

// before — incompatible combination
@ParameterizedClass
@TestInstance(Lifecycle.PER_CLASS)
class MyTest {
    private final int value;
    MyTest(@Parameter int value) { this.value = value; }
}

// after — use field injection with PER_CLASS
@ParameterizedClass
@TestInstance(Lifecycle.PER_CLASS)
class MyTest {
    @Parameter int value;
}

// OR — use constructor injection with PER_METHOD (default)
@ParameterizedClass
class MyTest {
    private final int value;
    MyTest(@Parameter int value) { this.value = value; }
}
Defensive patterns

Strategy: validation

Validate before calling

// Before annotating a @ParameterizedClass, check for the PER_CLASS + constructor injection conflict
import org.junit.jupiter.api.TestInstance;

boolean isPerClass = testClass.isAnnotationPresent(TestInstance.class)
    && testClass.getAnnotation(TestInstance.class).value() == TestInstance.Lifecycle.PER_CLASS;
boolean hasParameterizedConstructor = java.util.Arrays.stream(testClass.getDeclaredConstructors())
    .anyMatch(c -> c.getParameterCount() > 0);

if (isPerClass && hasParameterizedConstructor && testClass.isAnnotationPresent(ParameterizedClass.class)) {
    throw new IllegalStateException(
        "Constructor injection is not supported with @TestInstance(PER_CLASS). Use field injection (@Parameter) or switch to PER_METHOD.");
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of junit-team/junit5@f070c699a0 (2026-08-11). Data as JSON: /api/errors/4511c82b50a13304. Report an issue: GitHub.

Appendix: source

Thrown at junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedClassExtension.java:119

			return false;
		}

		store.put(DECLARATION_CONTEXT_KEY,
			createClassContext(extensionContext, extensionContext.getRequiredTestClass(), annotation.get()));

		return true;
	}

	private static ParameterizedClassContext createClassContext(ExtensionContext extensionContext, Class<?> testClass,
			ParameterizedClass annotation) {

		TestInstance.Lifecycle lifecycle = extensionContext.getTestInstanceLifecycle() //
				.orElseThrow(() -> new PreconditionViolationException("TestInstance.Lifecycle not present"));

		ParameterizedClassContext classContext = new ParameterizedClassContext(testClass, annotation, lifecycle);

		if (lifecycle == PER_CLASS && classContext.getInjectionType() == CONSTRUCTOR) {
			throw new PreconditionViolationException(
				"Constructor injection is not supported for @ParameterizedClass classes with @TestInstance(Lifecycle.PER_CLASS)");
		}

		return classContext;
	}

	private ParameterizedClassContext getDeclarationContext(ExtensionContext extensionContext) {
		return requireNonNull(getStore(extensionContext)//
				.get(DECLARATION_CONTEXT_KEY, ParameterizedClassContext.class));
	}

	private Store getStore(ExtensionContext context) {
		return context.getStore(Namespace.create(ParameterizedClassExtension.class, context.getRequiredTestClass()));
	}

}

View on GitHub (pinned to f070c699a0)