junit-team/junit5 · error · PreconditionViolationException
thread mode must not be INFERRED
Error message
thread mode must not be INFERRED
What it means
Thrown when TimeoutInvocationFactory.create() receives ThreadMode.INFERRED. INFERRED is a sentinel value that the JUnit engine resolves to either SAME_THREAD or SEPARATE_THREAD before reaching the factory — so passing INFERRED directly is an internal contract violation, not a user configuration error. This indicates a bug in the engine's internal dispatch logic or a custom extension that bypassed the resolution step.
Solutions
- Do not pass ThreadMode.INFERRED to create() — resolve it to SAME_THREAD or SEPARATE_THREAD first
- If you are writing a custom extension, use the engine's ThreadMode resolution logic before calling the factory
- Report as a JUnit bug if encountered through standard @Timeout annotations without custom extensions
Example fix
// before (incorrect direct call) factory.create(ThreadMode.INFERRED, parameters); // after (resolve to concrete mode first) ThreadMode resolved = determineThreadMode(); // SAME_THREAD or SEPARATE_THREAD factory.create(resolved, parameters);
Defensive patterns
Strategy: validation
Validate before calling
// Guard against passing INFERRED to the factory (internal use only) import org.junit.jupiter.engine.extension.TimeoutInvocationFactory; import org.junit.jupiter.api.Timeout.ThreadMode; ThreadMode safeMode = (threadMode == ThreadMode.INFERRED) ? ThreadMode.SAME_THREAD : threadMode; factory.create(safeMode, parameters);
Prevention
- Never pass ThreadMode.INFERRED directly to internal factory methods — always resolve it to SAME_THREAD or SEPARATE_THREAD first
- This error indicates an internal bug — report it to the JUnit team if encountered without custom extensions
When it happens
Trigger: Calling TimeoutInvocationFactory.create(ThreadMode.INFERRED, parameters) directly. This should not be reachable through normal @Timeout usage because the engine resolves INFERRED upstream. A custom extension that programmatically invokes the factory with an unresolved ThreadMode could trigger it.
Common situations: Essentially unreachable in normal usage. Would only appear if a custom extension or engine modification incorrectly passes ThreadMode.INFERRED to the factory without resolving it first. Developers writing extensions that interact with the timeout machinery may encounter this during development.
Related errors
- Scheduled executor could not be stopped in an orderly manner
- Timeout duration is not in the expected format
- AnnotationBasedArgumentsProvider does not override the…
- ArgumentsProvider does not override the…
- Assumption failed
AI-assisted analysis of junit-team/junit5@f070c699a0 (2026-08-11).
Data as JSON: /api/errors/02f4f27f6787759d.
Report an issue: GitHub.
Appendix: source
Thrown at junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactory.java:43
/**
* @since 5.9
*/
class TimeoutInvocationFactory {
private final Store store;
TimeoutInvocationFactory(Store store) {
this.store = Preconditions.notNull(store, "store must not be null");
}
<T extends @Nullable Object> Invocation<T> create(ThreadMode threadMode,
TimeoutInvocationParameters<T> parameters) {
Preconditions.notNull(parameters, "timeout invocation parameters must not be null");
return switch (Preconditions.notNull(threadMode, "thread mode must not be null")) {
case SAME_THREAD -> new SameThreadTimeoutInvocation<>(parameters,
getThreadExecutorForSameThreadInvocation());
case SEPARATE_THREAD -> new SeparateThreadTimeoutInvocation<>(parameters);
case INFERRED -> throw new PreconditionViolationException("thread mode must not be INFERRED");
};
}
@SuppressWarnings("resource")
private ScheduledExecutorService getThreadExecutorForSameThreadInvocation() {
return store.computeIfAbsent(SingleThreadExecutorResource.class).get();
}
@SuppressWarnings({ "deprecation", "try" })
abstract static class ExecutorResource implements Store.CloseableResource, AutoCloseable {
private final ScheduledExecutorService executor;
ExecutorResource(ScheduledExecutorService executor) {
this.executor = executor;
}
ScheduledExecutorService get() {View on GitHub (pinned to f070c699a0)