junit-team/junit5 · error · JUnitException

Scheduled executor could not be stopped in an orderly manner

Error message

Scheduled executor could not be stopped in an orderly manner

What it means

Thrown when the scheduled executor service used for timeout monitoring fails to terminate within 5 seconds of calling shutdown(), even after shutdownNow() is invoked. This indicates the timeout watcher thread is stuck — possibly blocked on an I/O operation, in an infinite loop, or holding a lock. The executor runs with Thread.MAX_PRIORITY under the name 'junit-jupiter-timeout-watcher'.

Solutions

  1. Ensure test code and lifecycle methods respond to Thread.interrupt() by checking Thread.currentThread().isInterrupted() or catching InterruptedException
  2. Close any network connections, file handles, or database resources in try-finally blocks so they don't block the watcher thread
  3. Increase timeout margins so the watched code completes well before the executor shutdown
  4. If the issue is environmental (CI under load), reduce parallel test execution or increase available CPU

Example fix

// before — blocking call that ignores interruption
while (!dataReady()) {
    Thread.sleep(100);
}

// after — cooperative with interruption
while (!dataReady()) {
    if (Thread.currentThread().isInterrupted()) {
        throw new RuntimeException("Interrupted while waiting");
    }
    Thread.sleep(100);
}
Defensive patterns

Strategy: try-catch

Try / catch

// If managing the executor lifecycle in a custom extension
try {
    executor.shutdown();
    if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
        executor.shutdownNow();
        logger.warning("Timeout watcher executor did not terminate cleanly");
    }
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
    executor.shutdownNow();
}

Prevention

When it happens

Trigger: The SAME_THREAD timeout invocation's ScheduledExecutorService is closed (typically at the end of the test context lifecycle) and its awaitTermination(5, SECONDS) returns false. This happens when the watcher thread is still executing or blocked. The underlying test code under timeout management is non-cooperative with thread interruption.

Common situations: Tests that spawn their own threads or perform blocking I/O (network calls, file locks, database waits) that don't respond to interruption. Long-running tearDown or @AfterEach methods that exceed the 5-second grace window. Tests running in environments with heavy GC pressure or thread starvation where the watcher thread doesn't get scheduled in time.

Related errors


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

Appendix: source

Thrown at junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactory.java:71

	abstract static class ExecutorResource implements Store.CloseableResource, AutoCloseable {

		private final ScheduledExecutorService executor;

		ExecutorResource(ScheduledExecutorService executor) {
			this.executor = executor;
		}

		ScheduledExecutorService get() {
			return executor;
		}

		@Override
		public void close() throws Exception {
			executor.shutdown();
			boolean terminated = executor.awaitTermination(5, TimeUnit.SECONDS);
			if (!terminated) {
				executor.shutdownNow();
				throw new JUnitException("Scheduled executor could not be stopped in an orderly manner");
			}
		}
	}

	@SuppressWarnings("try")
	static class SingleThreadExecutorResource extends ExecutorResource {

		@SuppressWarnings({ "unused", "ThreadPriorityCheck" })
		SingleThreadExecutorResource() {
			super(Executors.newSingleThreadScheduledExecutor(runnable -> {
				Thread thread = new Thread(runnable, "junit-jupiter-timeout-watcher");
				thread.setPriority(Thread.MAX_PRIORITY);
				return thread;
			}));
		}
	}

}

View on GitHub (pinned to f070c699a0)