JEP 499: Structured Concurrency
This on is HEAVY - leave it for the end of workshops
Last updated
This on is HEAVY - leave it for the end of workshops
Last updated
= whole "structured concurrency" section
"nurseries" :
- and why currently it is not the problem - you can not cancell tasks/coroutines
✅ Makes concurrent code easier to reason about
Tasks are scoped, just like variables in a block — when the scope ends, all tasks are done.
🔒 Ensures safe lifecycle management of threads
No more forgotten threads running in the background or leaking resources.
🔁 Joins and handles subtasks consistently
You explicitly wait for all subtasks to complete and handle their outcomes in one place.
📦 Propagates errors and cancellation properly
A failure in one task cancels the others and surfaces at the top level.
🧵 Plays perfectly with virtual threads
Structured scopes simplify and manage thousands of lightweight virtual threads safely.
🧼 Encourages clean and reliable resource usage
Threads are tied to lexical scopes, similar to try-with-resources or auto-closing blocks.
🛑 Makes cancellation cooperative and safe
You can shut down a scope and be confident that all work will stop quickly and gracefully.
🧠 Improves debuggability and predictability
Scopes encapsulate related tasks, making stack traces and failure analysis much clearer.
ShutdownOnSuccess
Stops when any subtask succeeds. Cancels others.
ShutdownOnFailure
Stops when any subtask fails. Cancels others.
StructuredTaskScope
Manual shutdown control — you must call shutdown()
explicitly.
Start with problem
It creates Task scope with two subtasks where each runs in its own virtual thread. Scope is created with custom thread factory so that virtual threads have names :
Run :
It presents that only "owner" can operate on scope. You can not call methods on scope from independet thread
Run :
It shows that failure of one task fails whole operation tree
Run :
It shows how to create sub scopes.
If you cancel a coroutine (i.e., a Job), and that coroutine catches the
CancellationException
and throws a different exception (like aDomainException
), the new exception will not be discarded — it will be propagated, and can cancel the parent and its siblings.
Because Kotlin uses CancellationException
as a signal to cancel a coroutine, swallowing or replacing it breaks the structured concurrency flow.
If you do this:
Then even though this coroutine was cancelled intentionally, you're reporting it as a failure.
So what happens?
✅ Yes, the DomainException
is handled (not ignored)...
😱 But, the coroutine framework will treat this as a failure, and cancel the parent and all siblings.