junit-team/junit5 · error · PreconditionViolationException

thread mode must not be INFERRED

Error message

thread mode must not be INFERRED

What it means

Thrown by TimeoutInvocationFactory.create() when the ThreadMode passed in is INFERRED. INFERRED is a sentinel meaning 'resolve from the junit.jupiter.execution.timeout.thread.mode.default configuration parameter', and TimeoutExtension.resolveTimeoutThreadMode() resolves it to SAME_THREAD or SEPARATE_THREAD before the factory is called. This throw is an internal precondition assertion: if INFERRED reaches the factory, the resolution logic upstream failed.

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 956246301e)

Solutions

  1. Resolve ThreadMode.INFERRED to SAME_THREAD or SEPARATE_THREAD before calling TimeoutInvocationFactory.create()
  2. If using the standard @Timeout annotation, ensure no custom extension intercepts and replaces the resolved thread mode with INFERRED
Defensive patterns

Strategy: validation

Validate before calling

// Before calling TimeoutInvocationFactory.create(), resolve INFERRED:
ThreadMode resolved = (threadMode == ThreadMode.INFERRED)
    ? ThreadMode.SAME_THREAD  // or read junit.jupiter.execution.timeout.thread.mode.default
    : threadMode;
assert resolved != ThreadMode.INFERRED;

Prevention

When it happens

Trigger: Calling TimeoutInvocationFactory.create(ThreadMode.INFERRED, parameters) directly. In normal JUnit operation, TimeoutExtension always resolves INFERRED before calling the factory, so this is only reachable by custom extensions that bypass TimeoutExtension's resolution step or by internal bugs.

Common situations: Not encountered in normal test usage. Could appear if a custom Extension directly instantiates TimeoutInvocationFactory and passes an unresolved ThreadMode.INFERRED.

Related errors


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