{"record":{"id":"ed6bf6e21579fff1","repo":"junit-team/junit5","slug":"not-on-a-worker-thread","errorCode":null,"errorMessage":"Not on a worker thread","messagePattern":"Not on a worker thread","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/WorkerThreadPoolHierarchicalTestExecutorService.java","lineNumber":277,"sourceCode":"\n\t\t@Nullable\n\t\tWorkerLease workerLease;\n\n\t\tWorkerThread(Runnable runnable, String name) {\n\t\t\tsuper(runnable, name);\n\t\t}\n\n\t\tstatic @Nullable WorkerThread get() {\n\t\t\tif (Thread.currentThread() instanceof WorkerThread workerThread) {\n\t\t\t\treturn workerThread;\n\t\t\t}\n\t\t\treturn null;\n\t\t}\n\n\t\tstatic WorkerThread getOrThrow() {\n\t\t\tvar workerThread = get();\n\t\t\tif (workerThread == null) {\n\t\t\t\tthrow new IllegalStateException(\"Not on a worker thread\");\n\t\t\t}\n\t\t\treturn workerThread;\n\t\t}\n\n\t\tWorkerThreadPoolHierarchicalTestExecutorService executor() {\n\t\t\treturn WorkerThreadPoolHierarchicalTestExecutorService.this;\n\t\t}\n\n\t\tvoid processQueueEntries(WorkerLease workerLease, BooleanSupplier doneCondition) {\n\t\t\tthis.workerLease = workerLease;\n\t\t\twhile (!executor.isShutdown()) {\n\t\t\t\tif (doneCondition.getAsBoolean()) {\n\t\t\t\t\tlogger.trace(() -> \"yielding resource lock\");\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif (workQueue.isEmpty()) {\n\t\t\t\t\tlogger.trace(() -> \"no queue entries available\");\n\t\t\t\t\tbreak;","sourceCodeStart":259,"sourceCodeEnd":295,"githubUrl":"https://github.com/junit-team/junit5/blob/f070c699a08b5d8393df9afd147af5c5e90bb21b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/WorkerThreadPoolHierarchicalTestExecutorService.java#L259-L295","documentation":"WorkerThread.getOrThrow() throws IllegalStateException when the current thread is not a WorkerThread (the executor's owned thread type). WorkerThreadPoolHierarchicalTestExecutorService associates per-thread state (leases, queue processing) only with its WorkerThread instances, so calling code that needs that state from any other thread is a programming error.","triggerScenarios":"Invoking WorkerThread.getOrThrow() (or internal code paths that call it) from a non-worker thread: a foreign ExecutorService thread, the main thread, a CompletableFuture default pool, or a thread the test itself spawned. This happens when test/extension code schedules work onto an external executor that then calls back into engine-internal APIs.","commonSituations":"A test or extension spawns its own threads / uses a shared ForkJoinPool.commonPool() and those threads touch engine internals expecting WorkerThread state; mixed executor services within one engine; a custom HierarchicalTestExecutorService whose tasks dispatch to an unrelated pool.","solutions":["Ensure any code that depends on WorkerThread state runs only inside tasks scheduled by the engine's own executor service.","Do not share engine-internal APIs (WorkerThread.getOrThrow) with externally spawned threads; capture needed context before leaving the worker thread.","If authoring a custom executor service, only call WorkerThread.getOrThrow() from threads you created as WorkerThread instances."],"exampleFix":"// before - runs on an external executor\notherExecutor.submit(() -> doWorkRequiringWorkerThread());\n// after - run on the engine worker, capture context beforehand\nvar captured = contextSnapshot;\nworker.submit(() -> doWorkWith(captured));","handlingStrategy":"validation","validationCode":"// Before calling worker-thread-dependent code, confirm the current thread\nif (!(Thread.currentThread() instanceof WorkerThread)) {\n    throw new IllegalStateException(\"must run on a worker thread\");\n}","typeGuard":"static boolean isWorkerThread() {\n    return Thread.currentThread().getClass().getName().endsWith(\"WorkerThread\")\n        || Thread.currentThread() instanceof org.junit.platform.engine.support.hierarchical.WorkerThreadPoolHierarchicalTestExecutorService.WorkerThread;\n}","tryCatchPattern":"try {\n    WorkerThread.getOrThrow();\n} catch (IllegalStateException e) {\n    // reschedule the work onto the engine's own executor\n}","preventionTips":["Only touch engine-internal thread state from tasks scheduled by the engine executor.","Capture needed context before dispatching to external executors.","Keep test-spawned threads away from engine internals."],"tags":["threading","worker-thread","executor-service","illegal-state"],"backgroundTag":null,"analyzedSha":"f070c699a08b5d8393df9afd147af5c5e90bb21b","analyzedAt":"2026-08-11T20:31:00.530Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}