junit-team/junit5 · error · IllegalStateException

Not on a worker thread

Error message

Not on a worker thread

What it means

Thrown by WorkerThread.getOrThrow() (line 274-280) as an IllegalStateException when Thread.currentThread() is not a WorkerThread of this executor. The WorkerThreadPool executor requires invokeAll() to be called from inside a worker thread it owns (documented at line 169-184); calling from any other thread violates the work-stealing invariants.

Source

Thrown at junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/WorkerThreadPoolHierarchicalTestExecutorService.java:277

		@Nullable
		WorkerLease workerLease;

		WorkerThread(Runnable runnable, String name) {
			super(runnable, name);
		}

		static @Nullable WorkerThread get() {
			if (Thread.currentThread() instanceof WorkerThread workerThread) {
				return workerThread;
			}
			return null;
		}

		static WorkerThread getOrThrow() {
			var workerThread = get();
			if (workerThread == null) {
				throw new IllegalStateException("Not on a worker thread");
			}
			return workerThread;
		}

		WorkerThreadPoolHierarchicalTestExecutorService executor() {
			return WorkerThreadPoolHierarchicalTestExecutorService.this;
		}

		void processQueueEntries(WorkerLease workerLease, BooleanSupplier doneCondition) {
			this.workerLease = workerLease;
			while (!executor.isShutdown()) {
				if (doneCondition.getAsBoolean()) {
					logger.trace(() -> "yielding resource lock");
					break;
				}
				if (workQueue.isEmpty()) {
					logger.trace(() -> "no queue entries available");
					break;

View on GitHub (pinned to 956246301e)

Solutions

  1. Never call invokeAll from outside a worker thread; submit tasks via submit(TestTask) which is safe to call from any thread (line 150-167).
  2. If you implement Node.execute, run child tasks synchronously or via the framework-provided executor, not on threads you create.
  3. Ensure your custom engine does not bypass the executor by invoking tasks on raw threads.
  4. Switch the offending code path to submit() and let the executor schedule the work.

Example fix

// before: calling invokeAll from a non-worker thread
executorService.invokeAll(childTasks); // throws 'Not on a worker thread'

// after: submit the parent task and let it invokeAll from within a worker
executorService.submit(parentTestTask); // parent's execute() may call invokeAll legally
Defensive patterns

Strategy: validation

Validate before calling

// WorkerThread is a nested type; expose a check via your engine if needed.
// Prefer: only call invokeAll from within a Node.Task submitted via submit().
boolean onWorker = Thread.currentThread().getClass().getName().endsWith("WorkerThread");

Try / catch

try {
    executorService.invokeAll(tasks);
} catch (IllegalStateException e) {
    if (e.getMessage().equals("Not on a worker thread")) {
        // re-submit a parent task instead
        executorService.submit(wrapAsParentTask(tasks));
        return;
    }
    throw e;
}

Prevention

When it happens

Trigger: Invoking WorkerThreadPoolHierarchicalTestExecutorService.invokeAll(...) from the main thread, a ForkJoinPool thread, or any thread that is not a WorkerThread belonging to this executor. Also reachable via WorkerThread.getOrThrow() inside processQueueEntries/runBlocking paths if somehow invoked off-worker.

Common situations: A custom Node.Task or extension that captures the HierarchicalTestExecutorService and calls invokeAll from a separate (non-worker) thread; mixing the WorkerThreadPool executor with code that spawns its own threads to drive test tasks; bugs in engines that submit tasks from outside the executor.

Related errors


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