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
- Ensure test code and lifecycle methods respond to Thread.interrupt() by checking Thread.currentThread().isInterrupted() or catching InterruptedException
- Close any network connections, file handles, or database resources in try-finally blocks so they don't block the watcher thread
- Increase timeout margins so the watched code completes well before the executor shutdown
- 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
- Ensure test code and lifecycle methods are responsive to Thread.interrupt() — check isInterrupted() in loops
- Close all I/O resources (sockets, files, DB connections) in try-finally blocks in test methods and @AfterEach
- Avoid infinite loops or unbounded blocking operations in tests that run under @Timeout
- Monitor for thread leaks in CI to detect stuck watcher threads early
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
- thread mode must not be INFERRED
- 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/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)