This is an automated email from the ASF dual-hosted git repository.
git-site-role pushed a commit to branch asf-site
in repository https://gitbox.apache.org/repos/asf/groovy-dev-site.git
The following commit(s) were added to refs/heads/asf-site by this push:
new 087b6ab 2026/04/06 07:38:28: Generated dev website from
groovy-website@2c4b454
087b6ab is described below
commit 087b6ab2c1de91bec86e306ef8593e4f7558b397
Author: jenkins <[email protected]>
AuthorDate: Mon Apr 6 07:38:28 2026 +0000
2026/04/06 07:38:28: Generated dev website from groovy-website@2c4b454
---
blog/feed.atom | 6 +-
blog/groovy-async-await.html | 686 ++++++++++++++++++-----------------------
blog/groovy-async-await_5.html | 671 ----------------------------------------
blog/index.html | 2 +-
search/search-index.json | 11 +-
5 files changed, 304 insertions(+), 1072 deletions(-)
diff --git a/blog/feed.atom b/blog/feed.atom
index cae691f..6106ec0 100644
--- a/blog/feed.atom
+++ b/blog/feed.atom
@@ -4,7 +4,7 @@
<link href="http://groovy.apache.org/blog"/>
<link href="http://groovy.apache.org/blog/feed.atom" rel="self"/>
<id>http://groovy.apache.org/blog</id>
- <updated>2026-04-02T10:00:00Z</updated>
+ <updated>2026-04-06T23:30:00Z</updated>
<entry>
<id>http://groovy.apache.org/blog/groovy-null-checker</id>
<author>
@@ -23,9 +23,9 @@
</author>
<title type="html">Async/await for Groovy&trade;</title>
<link href="http://groovy.apache.org/blog/groovy-async-await"/>
- <updated>2026-03-27T16:30:00Z</updated>
+ <updated>2026-04-06T23:30:00Z</updated>
<published>2026-03-27T16:30:00Z</published>
- <summary type="html">This post looks at a proposed extension to Groovy
which provides comprehensive async/await support.</summary>
+ <summary type="html">Groovy's async/await: write concurrent code that
reads like synchronous code, with virtual thread support, generators, channels,
&amp; structured concurrency.</summary>
</entry>
<entry>
<id>http://groovy.apache.org/blog/loop-invariants</id>
diff --git a/blog/groovy-async-await.html b/blog/groovy-async-await.html
index c26dd48..de7c63c 100644
--- a/blog/groovy-async-await.html
+++ b/blog/groovy-async-await.html
@@ -3,7 +3,7 @@
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]--><head>
- <meta charset='utf-8'/><meta http-equiv='X-UA-Compatible'
content='IE=edge'/><meta name='viewport' content='width=device-width,
initial-scale=1'/><meta name='keywords' content='async, await, concurrency,
virtual-threads'/><meta name='description' content='This post looks at a
proposed extension to Groovy which provides comprehensive async/await
support.'/><title>The Apache Groovy programming language - Blogs - Async/await
for Groovy™</title><link href='../img/favicon.ico' type= [...]
+ <meta charset='utf-8'/><meta http-equiv='X-UA-Compatible'
content='IE=edge'/><meta name='viewport' content='width=device-width,
initial-scale=1'/><meta name='keywords' content='async, await, concurrency,
virtual-threads'/><meta name='description' content='Groovy's async/await:
write concurrent code that reads like synchronous code, with virtual thread
support, generators, channels, & structured concurrency.'/><title>The
Apache Groovy programming language - Blogs - Async/awai [...]
</head><body>
<div id='fork-me'>
<a href='https://github.com/apache/groovy'>
@@ -59,123 +59,42 @@
</ul>
</div>
</div>
- </div><div id='content' class='page-1'><div
class='row'><div class='row-fluid'><div class='col-lg-3'><ul
class='nav-sidebar'><li><a href='./'>Blog index</a></li><li class='active'><a
href='#doc'>Async/await for Groovy™</a></li><li><a href='#_introduction'
class='anchor-link'>Introduction</a></li><li><a
href='#_choosing_the_right_tool' class='anchor-link'>Choosing the right
tool</a></li><li><a href='#_the_problem_callback_complexity'
class='anchor-link'>The p [...]
+ </div><div id='content' class='page-1'><div
class='row'><div class='row-fluid'><div class='col-lg-3'><ul
class='nav-sidebar'><li><a href='./'>Blog index</a></li><li class='active'><a
href='#doc'>Async/await for Groovy™</a></li><li><a href='#_introduction'
class='anchor-link'>Introduction</a></li><li><a href='#_getting_started'
class='anchor-link'>Getting started</a></li><li><a
href='#_running_tasks_in_parallel' class='anchor-link'>Running tasks in
parallel</ [...]
<a href="https://github.com/paulk-asert/" target="_blank" rel="noopener
noreferrer"><img style="border-radius:50%;height:48px;width:auto"
src="img/paulk-asert.png" alt="Paul King"></a>
<div style="display:grid;align-items:center;margin:0.1ex;padding:0ex">
<div><a href="https://github.com/paulk-asert/" target="_blank" rel="noopener
noreferrer"><span>Paul King</span></a></div>
<div><small><i>PMC Member</i></small></div>
</div>
- </div><br/><span>Published: 2026-03-27 04:30PM</span></p><hr/><div
class="sect1">
+ </div><br/><span>Published: 2026-03-27 04:30PM (Last updated:
2026-04-06 11:30PM)</span></p><hr/><div class="sect1">
<h2 id="_introduction">Introduction</h2>
<div class="sectionbody">
<div class="paragraph">
-<p>A proposed enhancement, targeted for Groovy 6,
-adds native <code>async</code>/<code>await</code> as a language-level feature
-(<a href="https://issues.apache.org/jira/browse/GROOVY-9381">GROOVY-9381</a>,
-<a href="https://github.com/apache/groovy/pull/2387">PR #2387</a>).
-Inspired by similar constructs in JavaScript, C#, Kotlin, and Swift,
-the proposal would let you write asynchronous code in a sequential, readable
-style — with first-class support for async streams, deferred cleanup,
-structured concurrency, Go-style channels, and framework adapters
-for Reactor and RxJava.</p>
+<p>Groovy 6 adds native <code>async</code>/<code>await</code> as a
language-level feature
+(<a href="https://issues.apache.org/jira/browse/GROOVY-9381">GROOVY-9381</a>).
+Write asynchronous code in a sequential, readable style —
+with support for generators, deferred cleanup, Go-style channels,
+structured concurrency, and framework adapters for Reactor and RxJava.</p>
</div>
<div class="paragraph">
-<p>On JDK 21+, async methods automatically leverage
+<p>On JDK 21+, async tasks automatically leverage
<a
href="https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Thread.html#ofVirtual()">virtual
threads</a>
-for optimal scalability.</p>
+for optimal scalability. On JDK 17–20, a cached thread pool provides
+correct behavior as a fallback.</p>
</div>
<div class="paragraph">
-<p>This is a comprehensive feature. Rather than cover every detail,
-this post walks through a handful of bite-sized examples that show
-what the day-to-day experience would feel like — and how it compares
-to what you’d write in plain Java today.</p>
+<p>To make the features concrete, the examples follow a running theme:
+building the backend for <em>Groovy Quest</em>, a fictitious online game
+where heroes battle villains across dungeons.</p>
</div>
</div>
</div>
<div class="sect1">
-<h2 id="_choosing_the_right_tool">Choosing the right tool</h2>
-<div class="sectionbody">
-<div class="paragraph">
-<p>The proposal provides several complementary features.
-Before diving in, here’s a quick guide to when you’d reach for
each:</p>
-</div>
-<table class="tableblock frame-all grid-all stretch">
-<colgroup>
-<col style="width: 25%;">
-<col style="width: 37.5%;">
-<col style="width: 37.5%;">
-</colgroup>
-<thead>
-<tr>
-<th class="tableblock halign-left valign-top">Feature</th>
-<th class="tableblock halign-left valign-top">Use when…</th>
-<th class="tableblock halign-left valign-top">Complements</th>
-</tr>
-</thead>
-<tbody>
-<tr>
-<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>async</code>/<code>await</code></p></td>
-<td class="tableblock halign-left valign-top"><p class="tableblock">You have
sequential steps that involve I/O or other blocking work and want code that
reads top-to-bottom.</p></td>
-<td class="tableblock halign-left valign-top"><p class="tableblock">Foundation
for everything else — the other features build on top of async methods and
closures.</p></td>
-</tr>
-<tr>
-<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>Awaitable.all</code> / <code>any</code></p></td>
-<td class="tableblock halign-left valign-top"><p class="tableblock">You need
to launch several independent tasks and collect (all) or race (first) their
results.</p></td>
-<td class="tableblock halign-left valign-top"><p class="tableblock">Pairs with
<code>async</code> closures to create the tasks that
<code>all</code>/<code>any</code> coordinate.</p></td>
-</tr>
-<tr>
-<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>yield return</code> / <code>for await</code></p></td>
-<td class="tableblock halign-left valign-top"><p
class="tableblock">You’re producing or consuming a <em>stream</em> of
values over time — paginated APIs, sensor data, log tailing.</p></td>
-<td class="tableblock halign-left valign-top"><p class="tableblock">Producer
uses <code>async</code> + <code>yield return</code>; consumer uses <code>for
await</code>. Back-pressure is automatic.</p></td>
-</tr>
-<tr>
-<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>defer</code></p></td>
-<td class="tableblock halign-left valign-top"><p class="tableblock">You
acquire resources at different points and want guaranteed cleanup without
nested <code>try</code>/<code>finally</code>.</p></td>
-<td class="tableblock halign-left valign-top"><p class="tableblock">Works
inside async methods <em>and</em> async closures. LIFO order mirrors Go’s
<code>defer</code>.</p></td>
-</tr>
-<tr>
-<td class="tableblock halign-left valign-top"><p class="tableblock">Channels
(<code>AsyncChannel</code>)</p></td>
-<td class="tableblock halign-left valign-top"><p class="tableblock">Two or
more tasks need to communicate — producer/consumer, fan-out/fan-in, or
rendezvous hand-off.</p></td>
-<td class="tableblock halign-left valign-top"><p class="tableblock">Created
with <code>AsyncChannel.create()</code>; consumed with <code>for await</code>;
launched with <code>Awaitable.go</code>.</p></td>
-</tr>
-<tr>
-<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>AsyncScope</code></p></td>
-<td class="tableblock halign-left valign-top"><p class="tableblock">You want
structured concurrency — child task lifetimes tied to a scope with automatic
cancellation.</p></td>
-<td class="tableblock halign-left valign-top"><p
class="tableblock">Groovy’s take on the same goal as JDK
<code>StructuredTaskScope</code>, with <code>async</code>/<code>await</code>
integration.</p></td>
-</tr>
-<tr>
-<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>AsyncContext</code></p></td>
-<td class="tableblock halign-left valign-top"><p class="tableblock">You need
contextual data (e.g. player session, logging trace ID) to follow your async
calls across thread hops.</p></td>
-<td class="tableblock halign-left valign-top"><p
class="tableblock">Automatically propagated through
<code>async</code>/<code>await</code>, <code>AsyncScope</code>, and
<code>Awaitable.go</code>.</p></td>
-</tr>
-<tr>
-<td class="tableblock halign-left valign-top"><p class="tableblock">Framework
adapters</p></td>
-<td class="tableblock halign-left valign-top"><p
class="tableblock">You’re already using Reactor or RxJava and want
<code>await</code> to work transparently with their types.</p></td>
-<td class="tableblock halign-left valign-top"><p
class="tableblock">Auto-discovered via <code>ServiceLoader</code> — just add
the dependency.</p></td>
-</tr>
-</tbody>
-</table>
-<div class="paragraph">
-<p>In practice, you’ll mix and match. A typical service handler might
-use <code>async</code>/<code>await</code> for its main flow,
<code>Awaitable.all</code> to fan out
-parallel calls, <code>defer</code> for cleanup, and <code>AsyncScope</code> to
ensure
-nothing leaks.</p>
-</div>
-<div class="paragraph">
-<p>To make the features concrete, the examples below follow a running
-theme: building the backend for <em>Groovy Quest</em>, a fictitious online game
-where heroes battle villains across dungeons. Each example tackles
-a different part of the game — loading heroes, spawning enemies,
-streaming dungeon waves, managing resources, and coordinating
-raid parties.</p>
-</div>
-</div>
-</div>
-<div class="sect1">
-<h2 id="_the_problem_callback_complexity">The problem: callback complexity</h2>
+<h2 id="_getting_started">Getting started</h2>
<div class="sectionbody">
+<div class="sect2">
+<h3 id="_the_problem_callback_complexity">The problem: callback complexity</h3>
<div class="paragraph">
-<p>Imagine a player logs in and we need to load their quest: look up
+<p>A player logs in and we need to load their quest: look up
their hero ID, fetch the hero’s class, then load their active quest.
With <code>CompletableFuture</code> the logic gets buried under plumbing:</p>
</div>
@@ -191,40 +110,48 @@ CompletableFuture<Quest> quest =
</div>
<div class="paragraph">
<p>Each <code>.thenCompose()</code> adds a nesting level, exception recovery is
-separated from the code that causes the exception, and the control
-flow reads inside-out. For this example, the simple chaining
-is manageable, but the complexity grows non-linearly with
-branching and error handling.</p>
+separated from the code that causes it, and the control flow reads
+inside-out.</p>
</div>
</div>
-</div>
-<div class="sect1">
-<h2 id="_example_1_loading_a_hero_reads_like_synchronous_code">Example 1:
loading a hero — reads like synchronous code</h2>
-<div class="sectionbody">
+<div class="sect2">
+<h3 id="_loading_a_hero_reads_like_synchronous_code">Loading a hero — reads
like synchronous code</h3>
<div class="paragraph">
-<p>With the proposed <code>async</code>/<code>await</code>, the same logic
becomes:</p>
+<p>With <code>async</code>/<code>await</code>, the same logic becomes:</p>
</div>
<div class="listingblock">
<div class="content">
-<pre class="prettyprint highlight"><code data-lang="groovy">async Quest
loadHeroQuest(String loginToken) {
+<pre class="prettyprint highlight"><code data-lang="groovy">Quest
loadHeroQuest(String loginToken) {
var heroId = await lookupHeroId(loginToken)
var heroClass = await fetchHeroClass(heroId)
- var quest = await loadActiveQuest(heroClass)
- return quest
+ return await loadActiveQuest(heroClass)
}</code></pre>
</div>
</div>
<div class="paragraph">
-<p>Variables are declared at the point of use. The return value is
-obvious. No callbacks, no lambdas, no chained combinators.</p>
+<p>Variables are declared at the point of use. The return value is obvious.
+No callbacks, no lambdas, no chained combinators. The method is a
+regular method — the caller decides whether to run it asynchronously:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="prettyprint highlight"><code data-lang="groovy">// Run
asynchronously:
+def quest = await async { loadHeroQuest(token) }
+
+// Or call directly (blocking — fine on virtual threads):
+def quest = loadHeroQuest(token)</code></pre>
+</div>
+</div>
</div>
+<div class="sect2">
+<h3 id="_exception_handling_just_trycatch">Exception handling — just
<code>try</code>/<code>catch</code></h3>
<div class="paragraph">
<p>What about the <code>.exceptionally(e → Quest.DEFAULT)</code>
fallback from
-the Java version? With <code>async</code>/<code>await</code>, it’s just
a <code>try</code>/<code>catch</code>:</p>
+the Java version?</p>
</div>
<div class="listingblock">
<div class="content">
-<pre class="prettyprint highlight"><code data-lang="groovy">async Quest
loadHeroQuest(String loginToken) {
+<pre class="prettyprint highlight"><code data-lang="groovy">Quest
loadHeroQuest(String loginToken) {
try {
var heroId = await lookupHeroId(loginToken)
var heroClass = await fetchHeroClass(heroId)
@@ -236,51 +163,46 @@ the Java version? With
<code>async</code>/<code>await</code>, it’s just a
</div>
</div>
<div class="paragraph">
-<p><code>await</code> automatically unwraps <code>CompletionException</code>,
so you catch
-the <em>original</em> exception type — <code>NoActiveQuestException</code>
here, not
-a <code>CompletionException</code> wrapper. Error handling reads exactly like
-synchronous code — no separate <code>.exceptionally()</code> callback bolted
-on at the end of a chain.</p>
+<p><code>await</code> unwraps <code>CompletionException</code> automatically,
so you catch
+the <em>original</em> exception type. Error handling reads exactly like
+synchronous code.</p>
+</div>
</div>
</div>
</div>
<div class="sect1">
-<h2 id="_example_2_preparing_for_battle_fetch_once_await_together">Example 2:
preparing for battle — fetch once, await together</h2>
+<h2 id="_running_tasks_in_parallel">Running tasks in parallel</h2>
<div class="sectionbody">
+<div class="sect2">
+<h3 id="_preparing_for_battle_awaitable_all">Preparing for battle —
<code>Awaitable.all</code></h3>
<div class="paragraph">
-<p>Before a battle, the game needs to load several things in parallel:
-the hero’s stats, their inventory, and the villain they’re about
-to face. Launching concurrent work and collecting the results is a
-common pattern. Here’s how it looks with <code>Awaitable.all</code>:</p>
+<p>Before a battle, the game loads the hero’s stats, inventory, and
+the villain — all in parallel:</p>
</div>
<div class="listingblock">
<div class="content">
-<pre class="prettyprint highlight"><code data-lang="groovy">async
prepareBattle(heroId, visibleVillainId) {
+<pre class="prettyprint highlight"><code data-lang="groovy">def
prepareBattle(heroId, visibleVillainId) {
var stats = async { fetchHeroStats(heroId) }
var inventory = async { fetchInventory(heroId) }
var villain = async { fetchVillain(visibleVillainId) }
- var (s, inv, v) = await stats(), inventory(), villain()
+ var (s, inv, v) = await Awaitable.all(stats, inventory, villain)
return new BattleScreen(s, inv, v)
}</code></pre>
</div>
</div>
<div class="paragraph">
-<p>Here, <code>async { … }</code> creates an async closure — a reusable
-block that doesn’t run until you call it.
-Invoking <code>stats()</code>, <code>inventory()</code>, and
<code>villain()</code> each launches its respective block concurrently and
returns an <code>Awaitable</code>.</p>
+<p>Each <code>async { …​ }</code> starts immediately on a
background thread.
+The <code>await stats, inventory, villain</code> expression waits for all three
+to complete — it’s shorthand for <code>await Awaitable.all(stats,
inventory, villain)</code>.
+Parentheses also work: <code>await(stats, inventory, villain)</code>.</p>
</div>
-<div class="paragraph">
-<p>The <code>await stats(), inventory(), villain()</code> statement is a
shorthand for
-<code>await Awaitable.all(stats(), inventory(), villain())</code>.
-The <code>all</code> combinator produces another <code>Awaitable</code> that
completes when every task has finished. If any task fails, the remaining tasks
still run to completion, and the first exception is thrown unwrapped. (For
fail-fast semantics — cancelling siblings as soon as one fails — see
<code>AsyncScope</code> in Example 6.)</p>
-</div>
-<div class="sect2">
-<h3 id="_how_this_compares_to_javas_structuredtaskscope">How this compares to
Java’s <code>StructuredTaskScope</code></h3>
+<div class="sect3">
+<h4 id="_how_this_compares_to_javas_structuredtaskscope">How this compares to
Java’s <code>StructuredTaskScope</code></h4>
<div class="paragraph">
<p>Java’s structured concurrency preview
-(<a href="https://openjdk.org/jeps/525">JEP 525</a>, previewing since JDK 21)
-provides a similar capability through <code>StructuredTaskScope</code>:</p>
+(<a href="https://openjdk.org/jeps/525">JEP 525</a>) provides a similar
+capability:</p>
</div>
<div class="listingblock">
<div class="content">
@@ -296,81 +218,77 @@ try (var scope = StructuredTaskScope.open()) {
</div>
</div>
<div class="paragraph">
-<p>The goals are aligned — both approaches bind task lifetimes to a
-scope and cancel siblings on failure. The Groovy version adds
-syntactic sugar (<code>await</code>, <code>all</code>) and integrates with the
same
-<code>async</code>/<code>await</code> model used everywhere else, whereas
Java’s API
-is deliberately lower-level and imperative. We’ll see more on
-how Groovy’s <code>AsyncScope</code> complements JDK structured
concurrency
-in <a href="#_example_6_the_raid_party">Example 6</a>.</p>
-</div>
-<div class="paragraph">
-<p>Note that this isn’t an exact equivalent of our Groovy example.
-The async factory-like closures are reusable. If you don’t need that
-flexibility, you can also use <code>Awaitable.go</code> to launch a one-off
task.
-This more closely mirrors the Java version:</p>
-</div>
-<div class="listingblock">
-<div class="content">
-<pre class="prettyprint highlight"><code data-lang="groovy">async
prepareBattle(heroId, visibleVillainId) {
- var stats = Awaitable.go { fetchHeroStats(heroId) }
- var inventory = Awaitable.go { fetchInventory(heroId) }
- var villain = Awaitable.go { fetchVillain(visibleVillainId) }
-
- await stats, inventory, villain
- return new BattleScreen(stats.get(), inventory.get(), villain.get())
-}</code></pre>
+<p>Both approaches bind task lifetimes to a scope. Groovy adds syntactic
+sugar (<code>await</code>, <code>all</code>) and integrates with the same
model used
+everywhere else, whereas Java’s API is deliberately lower-level.
+Groovy’s <code>AsyncScope</code> (covered later) brings the full
structured
+concurrency model.</p>
</div>
</div>
</div>
<div class="sect2">
-<h3 id="_the_flip_side_awaitable_any_first_one_wins">The flip side:
<code>Awaitable.any</code> — first one wins</h3>
+<h3 id="_capture_the_flag_awaitable_any">Capture the flag —
<code>Awaitable.any</code></h3>
<div class="paragraph">
-<p>Where <code>all</code> waits for <em>every</em> task, <code>any</code>
returns as soon as the
-<em>first</em> one completes — a race. Imagine a capture-the-flag battle
-where the hero and villain both dash for the flag:</p>
+<p>Where <code>all</code> waits for <em>every</em> task, <code>any</code>
returns the <em>first</em> to
+complete — a race:</p>
</div>
<div class="listingblock">
<div class="content">
-<pre class="prettyprint highlight"><code data-lang="groovy">async
captureTheFlag(hero, villain, flag) {
+<pre class="prettyprint highlight"><code data-lang="groovy">def
captureTheFlag(hero, villain, flag) {
var heroGrab = async { hero.grab(flag) }
var villainGrab = async { villain.grab(flag) }
- var winner = await Awaitable.any(heroGrab(), villainGrab())
+ var winner = await Awaitable.any(heroGrab, villainGrab)
println "$winner.name captured the flag!"
}</code></pre>
</div>
</div>
<div class="paragraph">
<p>The loser’s task still runs to completion in the background
-(use <code>AsyncScope</code> if you want the loser cancelled immediately).
-This is the same "race" pattern as JavaScript’s <code>Promise.race</code>
-or Go’s <code>select</code>.</p>
+(use <code>AsyncScope</code> for fail-fast cancellation).</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_other_combinators">Other combinators</h3>
+<div class="ulist">
+<ul>
+<li>
+<p><strong><code>Awaitable.first(a, b, c)</code></strong> — returns the first
<em>successful</em>
+result, ignoring individual failures. Like JavaScript’s
+<code>Promise.any()</code>. Useful for hedged requests and graceful
degradation.</p>
+</li>
+<li>
+<p><strong><code>Awaitable.allSettled(a, b)</code></strong> — waits for all
tasks to settle
+(succeed or fail) without throwing. Returns an <code>AwaitResult</code> list
+with <code>success</code>, <code>value</code>, and <code>error</code>
fields.</p>
+</li>
+</ul>
</div>
</div>
</div>
</div>
<div class="sect1">
-<h2
id="_example_3_dungeon_waves_async_streams_with_yield_return_and_for_await">Example
3: dungeon waves — async streams with <code>yield return</code> and <code>for
await</code></h2>
+<h2 id="_generators_and_streaming">Generators and streaming</h2>
<div class="sectionbody">
+<div class="sect2">
+<h3 id="_dungeon_waves_yield_return_and_for_await">Dungeon waves — <code>yield
return</code> and <code>for await</code></h3>
<div class="paragraph">
-<p>A dungeon sends waves of enemies at the hero. Each wave is fetched
-from the server (maybe procedurally generated), and the hero fights
-them as they arrive. This is a natural fit for <em>async streams</em>:
-<code>yield return</code> produces values lazily, and <code>for await</code>
consumes them.</p>
+<p>A dungeon sends waves of enemies. Each wave is generated on demand
+and the hero fights them as they arrive:</p>
</div>
<div class="listingblock">
<div class="content">
-<pre class="prettyprint highlight"><code data-lang="groovy">async
generateWaves(String dungeonId) {
- var depth = 1
- while (depth <= await dungeonDepth(dungeonId)) {
- var wave = await spawnEnemies(dungeonId, depth)
- yield return wave
- depth++
+<pre class="prettyprint highlight"><code data-lang="groovy">def
generateWaves(String dungeonId) {
+ async {
+ var depth = 1
+ while (depth <= dungeonDepth(dungeonId)) {
+ yield return spawnEnemies(dungeonId, depth)
+ depth++
+ }
}
}
-async runDungeon(hero, dungeonId) {
+def runDungeon(hero, dungeonId) {
for await (wave in generateWaves(dungeonId)) {
wave.each { villain -> hero.fight(villain) }
}
@@ -378,85 +296,68 @@ async runDungeon(hero, dungeonId) {
</div>
</div>
<div class="paragraph">
-<p>The producer yields each wave on demand. The consumer pulls them
-with <code>for await</code>. The runtime provides natural
<strong>back-pressure</strong> —
-the producer blocks on each <code>yield return</code> until the hero is ready
-for the next wave, preventing unbounded enemy spawning. No explicit
-queues, signals, or synchronization required.</p>
+<p>The producer yields each wave on demand. The consumer pulls with
+<code>for await</code>. Natural <strong>back-pressure</strong> — the producer
blocks on each
+<code>yield return</code> until the consumer is ready. No queues, signals, or
+synchronization.</p>
</div>
<div class="paragraph">
-<p>There’s no language-level equivalent in plain Java today.
-You’d typically reach for Reactor’s <code>Flux</code> or
RxJava’s <code>Flowable</code>, each of which
-brings its own operator vocabulary and mental model. With <code>for
await</code>,
-async iteration feels as natural as a regular <code>for</code> loop.</p>
+<p>Since generators return a standard <code>Iterable</code>, regular
<code>for</code> loops
+and Groovy collection methods (<code>collect</code>, <code>findAll</code>,
<code>take</code>) also
+work — <code>for await</code> is optional for generators but required for
+reactive types (Flux, Observable).</p>
+</div>
</div>
</div>
</div>
<div class="sect1">
-<h2 id="_example_4_entering_a_dungeon_defer_for_guaranteed_cleanup">Example 4:
entering a dungeon — <code>defer</code> for guaranteed cleanup</h2>
+<h2 id="_deferred_cleanup_defer">Deferred cleanup — <code>defer</code></h2>
<div class="sectionbody">
<div class="paragraph">
-<p>Before entering a dungeon, our hero summons a familiar (<em>spirit
pet</em>) and opens a
-magic portal. Both must be cleaned up when the quest ends, whether
-the hero triumphs or falls. The <code>defer</code> keyword schedules cleanup
-to run when the enclosing async method completes — multiple deferred
-blocks execute in LIFO order, exactly like
+<p>Before entering a dungeon, the hero summons a familiar and opens a
+portal. Both must be cleaned up when the quest ends. <code>defer</code>
schedules
+cleanup in LIFO order, like
<a href="https://go.dev/blog/defer-panic-and-recover">Go’s
<code>defer</code></a>:</p>
</div>
<div class="listingblock">
<div class="content">
-<pre class="prettyprint highlight"><code data-lang="groovy">async
enterDungeon(hero, dungeonId) {
- var familiar = hero.summonFamiliar()
- defer familiar.dismiss()
+<pre class="prettyprint highlight"><code data-lang="groovy">def
enterDungeon(hero, dungeonId) {
+ def task = async {
+ var familiar = hero.summonFamiliar()
+ defer familiar.dismiss()
- var portal = openPortal(dungeonId)
- defer portal.close()
+ var portal = openPortal(dungeonId)
+ defer portal.close()
- await hero.explore(portal, familiar)
+ hero.explore(portal, familiar)
+ }
+ await task
}</code></pre>
</div>
</div>
<div class="paragraph">
-<p><code>defer</code> also works inside async closures — handy for one-off
-tasks like a hero briefly powering up. Notice how the deferred
-cleanup runs <em>after</em> the body completes:</p>
-</div>
-<div class="listingblock">
-<div class="content">
-<pre class="prettyprint highlight"><code data-lang="groovy">def log = []
-def powerUp = async {
- defer { log << 'shield down' }
- log << 'shield up'
- 'charged'
-}
-def result = await powerUp()
-assert result == 'charged'
-assert log == ['shield up', 'shield down']</code></pre>
-</div>
-</div>
-<div class="paragraph">
-<p>This is cleaner than nested <code>try</code>/<code>finally</code> blocks,
especially when
-multiple resources are acquired at different points in the method
-or closure.</p>
+<p>Deferred actions always run — even when an exception occurs.
+This is cleaner than nested <code>try</code>/<code>finally</code> blocks when
multiple
+resources are acquired at different points.</p>
</div>
</div>
</div>
<div class="sect1">
-<h2 id="_example_5_the_villain_spawner_go_style_channels">Example 5: the
villain spawner — Go-style channels</h2>
+<h2 id="_diving_deeper">Diving deeper</h2>
<div class="sectionbody">
+<div class="sect2">
+<h3 id="_channels_the_villain_spawner">Channels — the villain spawner</h3>
<div class="paragraph">
-<p>In a boss fight, a villain factory spawns enemies in the background
-while the hero fights them as they appear. The two sides need to
-communicate without tight coupling — a perfect fit for CSP-style
-channels inspired by Go:</p>
+<p>In a boss fight, a villain factory spawns enemies while the hero
+fights them. Channels provide Go-style decoupled communication:</p>
</div>
<div class="listingblock">
<div class="content">
-<pre class="prettyprint highlight"><code data-lang="groovy">async
bossFight(hero, bossArena) {
+<pre class="prettyprint highlight"><code data-lang="groovy">def
bossFight(hero, bossArena) {
var enemies = AsyncChannel.create(3) // buffered channel
// Villain spawner — runs concurrently
- Awaitable.go {
+ async {
for (type in bossArena.spawnOrder) {
await enemies.send(new Villain(type))
}
@@ -473,31 +374,23 @@ channels inspired by Go:</p>
</div>
</div>
<div class="paragraph">
-<p>Channels support both unbuffered (rendezvous) and buffered modes.
-<code>for await</code> iterates received values until the channel is closed —
-the Groovy equivalent of Go’s <code>for range ch</code>. You can also
race
-channel operations with <code>Awaitable.any(…​)</code>, serving a
similar
-role to Go’s <code>select</code> statement.</p>
-</div>
+<p>Channels support unbuffered (rendezvous) and buffered modes.
+<code>for await</code> iterates until the channel is closed and drained.
+Channels implement <code>Iterable</code>, so regular <code>for</code> loops
work too.</p>
</div>
</div>
-<div class="sect1">
-<h2 id="_example_6_the_raid_party">Example 6: the raid party — structured
concurrency with <code>AsyncScope</code></h2>
-<div class="sectionbody">
+<div class="sect2">
+<h3 id="_structured_concurrency_the_raid_party">Structured concurrency — the
raid party</h3>
<div class="paragraph">
-<p>A raid sends multiple heroes to scout different dungeon rooms
-simultaneously. If any hero falls, the whole raid retreats.
-<code>AsyncScope</code> binds child task lifetimes to a scope — inspired by
-Kotlin’s <code>coroutineScope</code>, Swift’s
<code>TaskGroup</code>, and Java’s
-<code>StructuredTaskScope</code>. When the scope exits, all child tasks have
-completed or been canceled:</p>
+<p>A raid sends heroes to scout different rooms. If anyone falls, the
+raid retreats. <code>AsyncScope</code> binds child task lifetimes to a
scope:</p>
</div>
<div class="listingblock">
<div class="content">
-<pre class="prettyprint highlight"><code data-lang="groovy">async
raidDungeon(List<Hero> party, List<Room> rooms) {
- try(var scope = AsyncScope.create()) {
+<pre class="prettyprint highlight"><code data-lang="groovy">def
raidDungeon(List<Hero> party, List<Room> rooms) {
+ AsyncScope.withScope { scope ->
var missions = unique(party, rooms).collect { hero, room ->
- scope.async { await hero.scout(room) }
+ scope.async { hero.scout(room) }
}
missions.collect { await it } // all loot gathered
}
@@ -505,195 +398,205 @@ completed or been canceled:</p>
</div>
</div>
<div class="paragraph">
-<p>By default, <code>AsyncScope</code> uses fail-fast semantics: if any
hero’s
-scouting task throws (the hero falls), sibling tasks are cancelled
-immediately — the raid retreats.</p>
+<p>By default, <code>AsyncScope</code> uses <strong>fail-fast</strong>
semantics: if any task
+fails, siblings are cancelled immediately. The scope guarantees all
+children have completed when <code>withScope</code> returns.</p>
</div>
-<div class="sect2">
-<h3 id="_cancellation_and_timeouts">Cancellation and timeouts</h3>
+<div class="sect3">
+<h4 id="_timeouts">Timeouts</h4>
<div class="paragraph">
-<p>Cancellation is one of the trickiest parts of async programming —
-and one of `CompletableFuture’s biggest pain points. The proposal
-makes it straightforward. For instance, a raid might have a time
-limit — if the party takes too long, all scouting missions are
-cancelled:</p>
+<p>A raid with a time limit:</p>
</div>
<div class="listingblock">
<div class="content">
-<pre class="prettyprint highlight"><code data-lang="groovy">async
raidWithTimeLimit(List<Hero> party, List<Room> rooms) {
+<pre class="prettyprint highlight"><code data-lang="groovy">def
raidWithTimeLimit(List<Hero> party, List<Room> rooms) {
try {
- await Awaitable.orTimeout(raidDungeon(party, rooms), 30, SECONDS)
+ await Awaitable.orTimeoutMillis(
+ async { raidDungeon(party, rooms) }, 30_000)
} catch (TimeoutException e) {
party.each { it.retreat() }
- return [] // no loot this time
+ return []
}
}</code></pre>
</div>
</div>
<div class="paragraph">
-<p>When the timeout fires, the scope’s child tasks are cancelled and
-a <code>TimeoutException</code> is thrown — which you handle with an ordinary
-<code>catch</code>, just like any other error.</p>
-</div>
-<div class="paragraph">
-<p>In simple cases, you can also use <code>completeOnTimeout</code>:</p>
+<p>Or with a fallback value:</p>
</div>
<div class="listingblock">
<div class="content">
-<pre class="prettyprint highlight"><code data-lang="groovy">var boobyPrize =
['an old boot']
-var loot = await Awaitable.completeOnTimeout(raidDungeon(heroes, rooms),
boobyPrize, 30, SECONDS)</code></pre>
+<pre class="prettyprint highlight"><code data-lang="groovy">var loot = await
Awaitable.completeOnTimeoutMillis(
+ async { raidDungeon(heroes, rooms) }, ['an old boot'], 30_000)</code></pre>
</div>
</div>
</div>
-<div class="sect2">
-<h3 id="_complementing_jdk_structured_concurrency">Complementing JDK
structured concurrency</h3>
+<div class="sect3">
+<h4 id="_complementing_jdk_structured_concurrency">Complementing JDK
structured concurrency</h4>
<div class="paragraph">
-<p>Java’s <code>StructuredTaskScope</code>
-(<a href="https://openjdk.org/jeps/525">JEP 525</a>, previewing since JDK 21)
-brings structured concurrency to the platform. <code>AsyncScope</code> shares
-the same design goals — child lifetimes bounded by a parent scope,
-automatic cancellation on failure — but layers additional value
-on top:</p>
+<p><code>AsyncScope</code> shares the same design goals as Java’s
+<code>StructuredTaskScope</code> but adds:</p>
</div>
<div class="ulist">
<ul>
<li>
-<p><strong><code>async</code>/<code>await</code> integration.</strong> JDK
scopes use <code>fork()</code> and
-<code>join()</code> as separate steps; <code>AsyncScope</code> uses
<code>scope.async { …​ }</code>
-and <code>await</code>, keeping scoped work consistent with the rest of
-your async code.</p>
+<p><strong><code>async</code>/<code>await</code> integration</strong> —
<code>scope.async { …​ }</code> and
+<code>await</code> instead of <code>fork()</code> + <code>join()</code>.</p>
</li>
<li>
-<p><strong>Works on JDK 17+.</strong> <code>StructuredTaskScope</code>
requires JDK 21+ and
-is still a preview API. <code>AsyncScope</code> runs on JDK 17+ (using
-<code>ThreadLocal</code> fallback) and uses <code>ScopedValue</code> when
available on
-JDK 25+.</p>
+<p><strong>Works on JDK 17+</strong> — uses <code>ThreadLocal</code> (virtual
threads on 21+).</p>
</li>
<li>
-<p><strong>Composes with other async features.</strong> Inside a scope you can
-use <code>defer</code> for cleanup, <code>for await</code> to consume streams,
channels
-for inter-task communication, and <code>Awaitable.all</code>/<code>any</code>
for
-coordination — all within the same structured lifetime guarantee.</p>
+<p><strong>Composes with other features</strong> — <code>defer</code>,
<code>for await</code>, channels,
+and combinators all work inside a scope.</p>
</li>
<li>
-<p><strong>Groovy-idiomatic API.</strong> <code>AsyncScope.withScope { scope
→ … }</code> uses
-a closure, avoiding the <code>try</code>-with-resources boilerplate of
Java’s
-<code>scope.open()</code> / <code>scope.close()</code>.</p>
+<p><strong>Groovy-idiomatic API</strong> — <code>AsyncScope.withScope { scope
→ … }</code>
+with a closure, no <code>try</code>-with-resources boilerplate.</p>
</li>
</ul>
</div>
-<div class="paragraph">
-<p>Think of <code>AsyncScope</code> as Groovy’s opinionated take on the
same
-principle: structured concurrency is the safety net, and
-<code>async</code>/<code>await</code> is the ergonomic surface you interact
with daily.</p>
-</div>
</div>
</div>
+<div class="sect2">
+<h3 id="_framework_adapters">Framework adapters</h3>
+<div class="paragraph">
+<p><code>await</code> natively understands <code>CompletableFuture</code>,
<code>CompletionStage</code>,
+<code>Future</code>, and any type with a registered
<code>AwaitableAdapter</code>.</p>
</div>
-<div class="sect1">
-<h2 id="_example_7_game_event_streams_framework_integration">Example 7: game
event streams — framework integration</h2>
-<div class="sectionbody">
<div class="paragraph">
-<p>Many game backends already use reactive frameworks. The <code>await</code>
-keyword natively understands <code>CompletableFuture</code>,
-<code>CompletionStage</code>, <code>Future</code>, and
<code>Flow.Publisher</code>. For third-party
-frameworks, drop-in adapter modules are auto-discovered via
-<code>ServiceLoader</code>.</p>
+<p>Drop-in adapter modules are provided:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><strong><code>groovy-reactor</code></strong> — <code>await</code> on
<code>Mono</code>, <code>for await</code> over <code>Flux</code></p>
+</li>
+<li>
+<p><strong><code>groovy-rxjava</code></strong> — <code>await</code> on
<code>Single</code>/<code>Maybe</code>/<code>Completable</code>,
+<code>for await</code> over <code>Observable</code>/<code>Flowable</code></p>
+</li>
+</ul>
</div>
<div class="paragraph">
-<p>Here, heroes might asynchronously gain boosts in power (<em>buff</em>), and
we might be able to stream villain alerts from a dungeon’s alert feed.
With the appropriate adapters on the classpath, we can <code>await</code>
Reactor’s <code>Mono</code> and <code>Flux</code> or RxJava’s
<code>Single</code> and <code>Observable</code> directly:</p>
+<p>Without the adapter:</p>
</div>
<div class="listingblock">
<div class="content">
-<pre class="prettyprint highlight"><code data-lang="groovy">// With
groovy-reactor on the classpath:
-async heroBoosts(heroId) {
- var hero = await Mono.just(fetchHero(heroId))
- for await (boost in Flux.from(hero.activeBoostStream())) {
- hero.applyBoost(boost)
- }
-}
-
-// With groovy-rxjava on the classpath:
-async villainAlerts(dungeonId) {
- var dungeon = await Single.just(loadDungeon(dungeonId))
- for await (alert in Observable.from(dungeon.alertFeed())) {
- broadcastToParty(alert)
- }
-}</code></pre>
+<pre class="prettyprint highlight"><code data-lang="groovy">def result =
Single.just('hello').toCompletionStage().toCompletableFuture().join()</code></pre>
</div>
</div>
<div class="paragraph">
-<p>No manual adapter registration is needed — add the dependency and
-<code>await</code> works transparently with Reactor and RxJava types.</p>
+<p>With <code>groovy-rxjava</code> on the classpath:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="prettyprint highlight"><code data-lang="groovy">def result = await
Awaitable.from(Single.just('hello'))</code></pre>
+</div>
+</div>
</div>
</div>
</div>
<div class="sect1">
-<h2 id="_how_it_relates_to_gpars_and_virtual_threads">How it relates to GPars
and virtual threads</h2>
+<h2 id="_best_practices">Best practices</h2>
<div class="sectionbody">
+<div class="sect2">
+<h3 id="_prefer_returning_values_over_shared_mutation">Prefer returning values
over shared mutation</h3>
<div class="paragraph">
-<p>Readers of the
-<a href="https://groovy.apache.org/blog/gpars-meets-virtual-threads">GPars
meets virtual threads</a>
-blog post will recall that GPars provides parallel collections,
-actors, agents, and dataflow concurrency — and that it works well
-with virtual threads via custom executor services.</p>
+<p>Async closures run on separate threads. Mutating shared variables
+is a race condition:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="prettyprint highlight"><code data-lang="groovy">// UNSAFE
+var count = 0
+def tasks = (1..100).collect { async { count++ } }
+tasks.each { await it }
+// count may not be 100!</code></pre>
</div>
-<div class="paragraph">
-<p>The async/await proposal complements GPars rather than replacing
-it. GPars excels at data-parallel operations (<code>collectParallel</code>,
-<code>findAllParallel</code>) and actor-based designs. Async/await targets a
-different sweet spot: sequential-looking code that is actually
-asynchronous, with language-level support for streams, cleanup,
-structured concurrency, and framework bridging. If you’re calling
-microservices, paginating through APIs, or coordinating I/O-bound
-tasks, async/await gives you a concise way to express that without
-dropping into callback chains.</p>
</div>
<div class="paragraph">
-<p>Both approaches benefit from virtual threads on JDK 21+, and
-both can coexist in the same codebase.</p>
+<p>Return values and collect results instead:</p>
</div>
+<div class="listingblock">
+<div class="content">
+<pre class="prettyprint highlight"><code data-lang="groovy">// SAFE
+def tasks = (1..100).collect { n -> async { n } }
+def results = await Awaitable.all(*tasks)
+assert results.sum() == 5050</code></pre>
</div>
</div>
-<div class="sect1">
-<h2 id="_the_full_picture">The full picture</h2>
-<div class="sectionbody">
<div class="paragraph">
-<p>The examples above are only a taste. The complete proposal also includes
-async closures and lambdas, the <code>@Async</code> annotation (for Java-style
-declarations), other <code>Awaitable</code> combinators (<code>any</code>,
<code>allSettled</code>, <code>delay</code>),
-more details about <code>AsyncContext</code> for propagating trace and tenant
metadata across
-thread hops, cancellation support, and a pluggable adapter registry for
-custom async types. The full spec is available in the
-<a
href="https://github.com/apache/groovy/blob/GROOVY-9381_3/src/spec/doc/core-async-await.adoc">draft
documentation</a>.</p>
+<p>When shared mutable state is unavoidable, use the appropriate
+concurrency-aware type — <code>AtomicInteger</code> for a shared counter,
+or thread-safe types from <code>java.util.concurrent</code>.</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_choosing_the_right_tool">Choosing the right tool</h3>
+<table class="tableblock frame-all grid-all stretch">
+<colgroup>
+<col style="width: 40%;">
+<col style="width: 60%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Feature</th>
+<th class="tableblock halign-left valign-top">Use when…​</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>async</code>/<code>await</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Sequential
steps with I/O or blocking work.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>Awaitable.all</code> / <code>any</code> /
<code>first</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Launch
independent tasks, collect all, race them, or take first success.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>yield return</code> / <code>for await</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Producing
or consuming a stream of values.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>defer</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Guaranteed
cleanup without nested <code>try</code>/<code>finally</code>.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>AsyncChannel</code></p></td>
+<td class="tableblock halign-left valign-top"><p
class="tableblock">Producer/consumer communication between tasks.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>AsyncScope</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Child task
lifetimes tied to a scope with fail-fast cancellation.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Framework
adapters</p></td>
+<td class="tableblock halign-left valign-top"><p
class="tableblock">Transparent <code>await</code> / <code>for await</code> with
Reactor or RxJava types.</p></td>
+</tr>
+</tbody>
+</table>
</div>
</div>
</div>
<div class="sect1">
-<h2 id="_wed_love_your_feedback">We’d love your feedback</h2>
+<h2 id="_how_it_relates_to_gpars_and_virtual_threads">How it relates to GPars
and virtual threads</h2>
<div class="sectionbody">
<div class="paragraph">
-<p>The async/await feature is currently a proposal in
-<a href="https://github.com/apache/groovy/pull/2387">PR #2387</a>
-(tracking issue
-<a href="https://issues.apache.org/jira/browse/GROOVY-9381">GROOVY-9381</a>).
-This is a substantial addition to the language and we want to get
-it right.</p>
+<p>Readers of the
+<a href="https://groovy.apache.org/blog/gpars-meets-virtual-threads">GPars
meets virtual threads</a>
+blog post will recall that GPars provides parallel collections,
+actors, agents, and dataflow concurrency.</p>
</div>
-<div class="ulist">
-<ul>
-<li>
-<p><strong>Comment</strong> on the <a
href="https://github.com/apache/groovy/pull/2387">PR</a> or
-the <a href="https://issues.apache.org/jira/browse/GROOVY-9381">JIRA issue</a>
-with your thoughts, use cases, or design suggestions.</p>
-</li>
-<li>
-<p><strong>Vote</strong> on the JIRA issue if you’d like to see this
feature land.</p>
-</li>
-</ul>
+<div class="paragraph">
+<p>Async/await complements GPars rather than replacing it. GPars
+excels at data-parallel operations and actor-based designs.
+Async/await targets sequential-looking code that is actually
+asynchronous, with language-level support for streams, cleanup,
+structured concurrency, and framework bridging.</p>
</div>
<div class="paragraph">
-<p>Your feedback helps us gauge interest and shape the final design.</p>
+<p>Both approaches benefit from virtual threads on JDK 21+, and
+both can coexist in the same codebase.</p>
</div>
</div>
</div>
@@ -701,14 +604,18 @@ with your thoughts, use cases, or design suggestions.</p>
<h2 id="_conclusion">Conclusion</h2>
<div class="sectionbody">
<div class="paragraph">
-<p>Through our <em>Groovy Quest</em> examples we’ve seen how the proposed
-async/await feature lets you write async Groovy code that reads
-almost like synchronous code — from loading a hero’s quest, to
-preparing a battle in parallel, streaming dungeon waves, cleaning
-up summoned familiars, coordinating a boss fight over channels,
-and rallying a raid party with structured concurrency. The syntax
-is concise, the mental model is straightforward, and virtual
-threads make it scale.</p>
+<p>Through our <em>Groovy Quest</em> examples we’ve seen how async/await
lets
+you write concurrent code that reads like synchronous code — from
+loading a hero’s quest, to preparing a battle in parallel, streaming
+dungeon waves, cleaning up summoned familiars, coordinating a boss
+fight over channels, and rallying a raid party with structured
+concurrency.</p>
+</div>
+<div class="paragraph">
+<p>The design philosophy is simple: closures run on real threads (virtual
+when available), stack traces are preserved, exceptions propagate
+naturally, and there’s no function coloring. The caller decides
what’s
+concurrent — not the method signature.</p>
</div>
</div>
</div>
@@ -718,16 +625,10 @@ threads make it scale.</p>
<div class="ulist">
<ul>
<li>
-<p><a href="https://github.com/apache/groovy/pull/2387">PR #2387 — Async/await
support</a></p>
-</li>
-<li>
<p><a href="https://issues.apache.org/jira/browse/GROOVY-9381">GROOVY-9381 —
Tracking issue</a></p>
</li>
<li>
-<p><a
href="https://github.com/apache/groovy/blob/GROOVY-9381_3/src/spec/doc/core-async-await.adoc">Draft
spec documentation</a></p>
-</li>
-<li>
-<p><a href="https://openjdk.org/jeps/525">JEP 525 — Structured Concurrency
(Sixth Preview)</a></p>
+<p><a href="https://openjdk.org/jeps/525">JEP 525 — Structured
Concurrency</a></p>
</li>
<li>
<p><a href="https://groovy.apache.org/blog/gpars-meets-virtual-threads">GPars
meets Virtual Threads</a></p>
@@ -737,6 +638,15 @@ threads make it scale.</p>
</li>
</ul>
</div>
+<div class="sidebarblock">
+<div class="content">
+<div class="title">Update history</div>
+<div class="paragraph">
+<p><strong>27/Mar/2026</strong>: Initial version.<br>
+<strong>06/Apr/2026</strong>: Revised version after feedback including
numerous simplifications.</p>
+</div>
+</div>
+</div>
</div>
</div></div></div></div></div><footer id='footer'>
<div class='row'>
diff --git a/blog/groovy-async-await_5.html b/blog/groovy-async-await_5.html
deleted file mode 100644
index 9a9d203..0000000
--- a/blog/groovy-async-await_5.html
+++ /dev/null
@@ -1,671 +0,0 @@
-<!DOCTYPE html>
-<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
-<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
-<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
-<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]--><head>
- <meta charset='utf-8'/><meta http-equiv='X-UA-Compatible'
content='IE=edge'/><meta name='viewport' content='width=device-width,
initial-scale=1'/><meta name='keywords' content='async, await, concurrency,
virtual-threads'/><meta name='description' content='This post introduces
Groovy's simplified async/await feature — write concurrent code that reads
like synchronous code, with virtual thread support, generators, channels, and
structured concurrency.'/><title>The Apache Groovy pr [...]
-</head><body>
- <div id='fork-me'>
- <a href='https://github.com/apache/groovy'>
- <img style='position: fixed; top: 20px; right: -58px; border: 0;
z-index: 100; transform: rotate(45deg);'
src='../img/horizontal-github-ribbon.png'/>
- </a>
- </div><div id='st-container' class='st-container st-effect-9'>
- <nav class='st-menu st-effect-9' id='menu-12'>
- <h2 class='icon icon-lab'>Socialize</h2><ul>
- <li>
- <a href='https://groovy-lang.org/mailing-lists.html'
class='icon'><span class='fa fa-classic fa-regular fa-envelope'></span> Discuss
on the mailing list</a>
- </li><li>
- <a href='https://x.com/ApacheGroovy' class='icon'><span
class='fa fa-brands fa-x-twitter'></span> Groovy on X</a>
- </li><li>
- <a href='https://bsky.app/profile/groovy.apache.org'
class='icon'><span class='fa fa-brands fa-bluesky'></span> Groovy on Bluesky</a>
- </li><li>
- <a href='https://fosstodon.org/@ApacheGroovy'
class='icon'><span class='fa fa-brands fa-mastodon'></span> Groovy on
Mastodon</a>
- </li><li>
- <a
href='https://www.linkedin.com/company/106402668/admin/dashboard/'
class='icon'><span class='fa fa-brands fa-linkedin'></span> Groovy on
LinkedIn</a>
- </li><li>
- <a href='https://groovy-lang.org/events.html'
class='icon'><span class='fa fa-classic fa-solid fa-calendar-days'></span>
Events and conferences</a>
- </li><li>
- <a href='https://github.com/apache/groovy'
class='icon'><span class='fa fa-brands fa-github'></span> Source code on
GitHub</a>
- </li><li>
- <a href='https://groovy-lang.org/reporting-issues.html'
class='icon'><span class='fa fa-classic fa-solid fa-bug'></span> Report issues
in Jira</a>
- </li><li>
- <a href='http://stackoverflow.com/questions/tagged/groovy'
class='icon'><span class='fa fa-brands fa-stack-overflow'></span> Stack
Overflow questions</a>
- </li><li>
- <a href='http://www.groovycommunity.com/'
class='icon'><span class='fa fa-brands fa-slack'></span> Slack Community</a>
- </li>
- </ul>
- </nav><div class='st-pusher'>
- <div class='st-content'>
- <div class='st-content-inner'>
- <!--[if lt IE 7]>
- <p class="browsehappy">You are using an
<strong>outdated</strong> browser. Please <a
href="http://browsehappy.com/">upgrade your browser</a> to improve your
experience.</p>
- <![endif]--><div><div class='navbar navbar-default
navbar-static-top' role='navigation'>
- <div class='container'>
- <div class='navbar-header'>
- <button type='button'
class='navbar-toggle' data-toggle='collapse' data-target='.navbar-collapse'>
- <span class='sr-only'></span><span
class='icon-bar'></span><span class='icon-bar'></span><span
class='icon-bar'></span>
- </button><a class='navbar-brand'
href='../index.html'>
- <i class='fa-classic fa-solid
fa-star'></i> Apache Groovy™
- </a>
- </div><div class='navbar-collapse collapse'>
- <ul class='nav navbar-nav navbar-right'>
- <li class=''><a
href='https://groovy-lang.org/learn.html'>Learn</a></li><li class=''><a
href='https://groovy-lang.org/documentation.html'>Documentation</a></li><li
class=''><a href='/download.html'>Download</a></li><li class=''><a
href='https://groovy-lang.org/support.html'>Support</a></li><li class=''><a
href='/'>Contribute</a></li><li class=''><a
href='https://groovy-lang.org/ecosystem.html'>Ecosystem</a></li><li class=''><a
href='/blog'>Blog pos [...]
- <a data-effect='st-effect-9'
class='st-trigger' href='#'>Socialize</a>
- </li><li class=''>
- <a href='../search.html'>
- <i class='fa-classic fa-solid
fa-magnifying-glass'></i>
- </a>
- </li>
- </ul>
- </div>
- </div>
- </div><div id='content' class='page-1'><div
class='row'><div class='row-fluid'><div class='col-lg-3'><ul
class='nav-sidebar'><li><a href='./'>Blog index</a></li><li class='active'><a
href='#doc'>Async/await for Groovy™</a></li><li><a href='#_introduction'
class='anchor-link'>Introduction</a></li><li><a href='#_getting_started'
class='anchor-link'>Getting started</a></li><li><a
href='#_running_tasks_in_parallel' class='anchor-link'>Running tasks in
parallel</ [...]
-<a href="https://github.com/paulk-asert/" target="_blank" rel="noopener
noreferrer"><img style="border-radius:50%;height:48px;width:auto"
src="img/paulk-asert.png" alt="Paul King"></a>
-<div style="display:grid;align-items:center;margin:0.1ex;padding:0ex">
- <div><a href="https://github.com/paulk-asert/" target="_blank" rel="noopener
noreferrer"><span>Paul King</span></a></div>
- <div><small><i>PMC Member</i></small></div>
-</div>
- </div><br/><span>Published: 2026-04-03 10:00AM</span></p><hr/><div
class="sect1">
-<h2 id="_introduction">Introduction</h2>
-<div class="sectionbody">
-<div class="paragraph">
-<p>Groovy 6 adds native <code>async</code>/<code>await</code> as a
language-level feature
-(<a href="https://issues.apache.org/jira/browse/GROOVY-9381">GROOVY-9381</a>).
-Write asynchronous code in a sequential, readable style —
-with support for generators, deferred cleanup, Go-style channels,
-structured concurrency, and framework adapters for Reactor and RxJava.</p>
-</div>
-<div class="paragraph">
-<p>On JDK 21+, async tasks automatically leverage
-<a
href="https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Thread.html#ofVirtual()">virtual
threads</a>
-for optimal scalability. On JDK 17–20, a cached thread pool provides
-correct behavior as a fallback.</p>
-</div>
-<div class="paragraph">
-<p>To make the features concrete, the examples follow a running theme:
-building the backend for <em>Groovy Quest</em>, a fictitious online game
-where heroes battle villains across dungeons.</p>
-</div>
-</div>
-</div>
-<div class="sect1">
-<h2 id="_getting_started">Getting started</h2>
-<div class="sectionbody">
-<div class="sect2">
-<h3 id="_the_problem_callback_complexity">The problem: callback complexity</h3>
-<div class="paragraph">
-<p>A player logs in and we need to load their quest: look up
-their hero ID, fetch the hero’s class, then load their active quest.
-With <code>CompletableFuture</code> the logic gets buried under plumbing:</p>
-</div>
-<div class="listingblock">
-<div class="content">
-<pre class="prettyprint highlight"><code data-lang="java">// Java with
CompletableFuture
-CompletableFuture<Quest> quest =
- lookupHeroId(loginToken)
- .thenCompose(id -> fetchHeroClass(id))
- .thenCompose(heroClass -> loadActiveQuest(heroClass))
- .exceptionally(e -> Quest.DEFAULT);</code></pre>
-</div>
-</div>
-<div class="paragraph">
-<p>Each <code>.thenCompose()</code> adds a nesting level, exception recovery is
-separated from the code that causes it, and the control flow reads
-inside-out.</p>
-</div>
-</div>
-<div class="sect2">
-<h3 id="_loading_a_hero_reads_like_synchronous_code">Loading a hero — reads
like synchronous code</h3>
-<div class="paragraph">
-<p>With <code>async</code>/<code>await</code>, the same logic becomes:</p>
-</div>
-<div class="listingblock">
-<div class="content">
-<pre class="prettyprint highlight"><code data-lang="groovy">Quest
loadHeroQuest(String loginToken) {
- var heroId = await lookupHeroId(loginToken)
- var heroClass = await fetchHeroClass(heroId)
- return await loadActiveQuest(heroClass)
-}</code></pre>
-</div>
-</div>
-<div class="paragraph">
-<p>Variables are declared at the point of use. The return value is obvious.
-No callbacks, no lambdas, no chained combinators. The method is a
-regular method — the caller decides whether to run it asynchronously:</p>
-</div>
-<div class="listingblock">
-<div class="content">
-<pre class="prettyprint highlight"><code data-lang="groovy">// Run
asynchronously:
-def quest = await async { loadHeroQuest(token) }
-
-// Or call directly (blocking — fine on virtual threads):
-def quest = loadHeroQuest(token)</code></pre>
-</div>
-</div>
-</div>
-<div class="sect2">
-<h3 id="_exception_handling_just_trycatch">Exception handling — just
<code>try</code>/<code>catch</code></h3>
-<div class="paragraph">
-<p>What about the <code>.exceptionally(e → Quest.DEFAULT)</code>
fallback from
-the Java version?</p>
-</div>
-<div class="listingblock">
-<div class="content">
-<pre class="prettyprint highlight"><code data-lang="groovy">Quest
loadHeroQuest(String loginToken) {
- try {
- var heroId = await lookupHeroId(loginToken)
- var heroClass = await fetchHeroClass(heroId)
- return await loadActiveQuest(heroClass)
- } catch (NoActiveQuestException e) {
- return Quest.DEFAULT
- }
-}</code></pre>
-</div>
-</div>
-<div class="paragraph">
-<p><code>await</code> unwraps <code>CompletionException</code> automatically,
so you catch
-the <em>original</em> exception type. Error handling reads exactly like
-synchronous code.</p>
-</div>
-</div>
-</div>
-</div>
-<div class="sect1">
-<h2 id="_running_tasks_in_parallel">Running tasks in parallel</h2>
-<div class="sectionbody">
-<div class="sect2">
-<h3 id="_preparing_for_battle_awaitable_all">Preparing for battle —
<code>Awaitable.all</code></h3>
-<div class="paragraph">
-<p>Before a battle, the game loads the hero’s stats, inventory, and
-the villain — all in parallel:</p>
-</div>
-<div class="listingblock">
-<div class="content">
-<pre class="prettyprint highlight"><code data-lang="groovy">def
prepareBattle(heroId, visibleVillainId) {
- var stats = async { fetchHeroStats(heroId) }
- var inventory = async { fetchInventory(heroId) }
- var villain = async { fetchVillain(visibleVillainId) }
-
- var (s, inv, v) = await Awaitable.all(stats, inventory, villain)
- return new BattleScreen(s, inv, v)
-}</code></pre>
-</div>
-</div>
-<div class="paragraph">
-<p>Each <code>async { …​ }</code> starts immediately on a
background thread.
-The <code>await stats, inventory, villain</code> expression waits for all three
-to complete — it’s shorthand for <code>await Awaitable.all(stats,
inventory, villain)</code>.
-Parentheses also work: <code>await(stats, inventory, villain)</code>.</p>
-</div>
-<div class="sect3">
-<h4 id="_how_this_compares_to_javas_structuredtaskscope">How this compares to
Java’s <code>StructuredTaskScope</code></h4>
-<div class="paragraph">
-<p>Java’s structured concurrency preview
-(<a href="https://openjdk.org/jeps/525">JEP 525</a>) provides a similar
-capability:</p>
-</div>
-<div class="listingblock">
-<div class="content">
-<pre class="prettyprint highlight"><code data-lang="java">// Java with
StructuredTaskScope (JDK 25 preview API)
-try (var scope = StructuredTaskScope.open()) {
- var statsTask = scope.fork(() -> fetchHeroStats(heroId));
- var inventoryTask = scope.fork(() -> fetchInventory(heroId));
- var villainTask = scope.fork(() -> fetchVillain(villainId));
- scope.join();
- return new BattleScreen(
- statsTask.get(), inventoryTask.get(), villainTask.get());
-}</code></pre>
-</div>
-</div>
-<div class="paragraph">
-<p>Both approaches bind task lifetimes to a scope. Groovy adds syntactic
-sugar (<code>await</code>, <code>all</code>) and integrates with the same
model used
-everywhere else, whereas Java’s API is deliberately lower-level.
-Groovy’s <code>AsyncScope</code> (covered later) brings the full
structured
-concurrency model.</p>
-</div>
-</div>
-</div>
-<div class="sect2">
-<h3 id="_capture_the_flag_awaitable_any">Capture the flag —
<code>Awaitable.any</code></h3>
-<div class="paragraph">
-<p>Where <code>all</code> waits for <em>every</em> task, <code>any</code>
returns the <em>first</em> to
-complete — a race:</p>
-</div>
-<div class="listingblock">
-<div class="content">
-<pre class="prettyprint highlight"><code data-lang="groovy">def
captureTheFlag(hero, villain, flag) {
- var heroGrab = async { hero.grab(flag) }
- var villainGrab = async { villain.grab(flag) }
-
- var winner = await Awaitable.any(heroGrab, villainGrab)
- println "$winner.name captured the flag!"
-}</code></pre>
-</div>
-</div>
-<div class="paragraph">
-<p>The loser’s task still runs to completion in the background
-(use <code>AsyncScope</code> for fail-fast cancellation).</p>
-</div>
-</div>
-<div class="sect2">
-<h3 id="_other_combinators">Other combinators</h3>
-<div class="ulist">
-<ul>
-<li>
-<p><strong><code>Awaitable.first(a, b, c)</code></strong> — returns the first
<em>successful</em>
-result, ignoring individual failures. Like JavaScript’s
-<code>Promise.any()</code>. Useful for hedged requests and graceful
degradation.</p>
-</li>
-<li>
-<p><strong><code>Awaitable.allSettled(a, b)</code></strong> — waits for all
tasks to settle
-(succeed or fail) without throwing. Returns an <code>AwaitResult</code> list
-with <code>success</code>, <code>value</code>, and <code>error</code>
fields.</p>
-</li>
-</ul>
-</div>
-</div>
-</div>
-</div>
-<div class="sect1">
-<h2 id="_generators_and_streaming">Generators and streaming</h2>
-<div class="sectionbody">
-<div class="sect2">
-<h3 id="_dungeon_waves_yield_return_and_for_await">Dungeon waves — <code>yield
return</code> and <code>for await</code></h3>
-<div class="paragraph">
-<p>A dungeon sends waves of enemies. Each wave is generated on demand
-and the hero fights them as they arrive:</p>
-</div>
-<div class="listingblock">
-<div class="content">
-<pre class="prettyprint highlight"><code data-lang="groovy">def
generateWaves(String dungeonId) {
- async {
- var depth = 1
- while (depth <= dungeonDepth(dungeonId)) {
- yield return spawnEnemies(dungeonId, depth)
- depth++
- }
- }
-}
-
-def runDungeon(hero, dungeonId) {
- for await (wave in generateWaves(dungeonId)) {
- wave.each { villain -> hero.fight(villain) }
- }
-}</code></pre>
-</div>
-</div>
-<div class="paragraph">
-<p>The producer yields each wave on demand. The consumer pulls with
-<code>for await</code>. Natural <strong>back-pressure</strong> — the producer
blocks on each
-<code>yield return</code> until the consumer is ready. No queues, signals, or
-synchronization.</p>
-</div>
-<div class="paragraph">
-<p>Since generators return a standard <code>Iterable</code>, regular
<code>for</code> loops
-and Groovy collection methods (<code>collect</code>, <code>findAll</code>,
<code>take</code>) also
-work — <code>for await</code> is optional for generators but required for
-reactive types (Flux, Observable).</p>
-</div>
-</div>
-</div>
-</div>
-<div class="sect1">
-<h2 id="_deferred_cleanup_defer">Deferred cleanup — <code>defer</code></h2>
-<div class="sectionbody">
-<div class="paragraph">
-<p>Before entering a dungeon, the hero summons a familiar and opens a
-portal. Both must be cleaned up when the quest ends. <code>defer</code>
schedules
-cleanup in LIFO order, like
-<a href="https://go.dev/blog/defer-panic-and-recover">Go’s
<code>defer</code></a>:</p>
-</div>
-<div class="listingblock">
-<div class="content">
-<pre class="prettyprint highlight"><code data-lang="groovy">def
enterDungeon(hero, dungeonId) {
- def task = async {
- var familiar = hero.summonFamiliar()
- defer familiar.dismiss()
-
- var portal = openPortal(dungeonId)
- defer portal.close()
-
- hero.explore(portal, familiar)
- }
- await task
-}</code></pre>
-</div>
-</div>
-<div class="paragraph">
-<p>Deferred actions always run — even when an exception occurs.
-This is cleaner than nested <code>try</code>/<code>finally</code> blocks when
multiple
-resources are acquired at different points.</p>
-</div>
-</div>
-</div>
-<div class="sect1">
-<h2 id="_diving_deeper">Diving deeper</h2>
-<div class="sectionbody">
-<div class="sect2">
-<h3 id="_channels_the_villain_spawner">Channels — the villain spawner</h3>
-<div class="paragraph">
-<p>In a boss fight, a villain factory spawns enemies while the hero
-fights them. Channels provide Go-style decoupled communication:</p>
-</div>
-<div class="listingblock">
-<div class="content">
-<pre class="prettyprint highlight"><code data-lang="groovy">def
bossFight(hero, bossArena) {
- var enemies = AsyncChannel.create(3) // buffered channel
-
- // Villain spawner — runs concurrently
- async {
- for (type in bossArena.spawnOrder) {
- await enemies.send(new Villain(type))
- }
- enemies.close()
- }
-
- // Hero fights each enemy as it arrives
- var xp = 0
- for await (villain in enemies) {
- xp += hero.fight(villain)
- }
- return xp
-}</code></pre>
-</div>
-</div>
-<div class="paragraph">
-<p>Channels support unbuffered (rendezvous) and buffered modes.
-<code>for await</code> iterates until the channel is closed and drained.
-Channels implement <code>Iterable</code>, so regular <code>for</code> loops
work too.</p>
-</div>
-</div>
-<div class="sect2">
-<h3 id="_structured_concurrency_the_raid_party">Structured concurrency — the
raid party</h3>
-<div class="paragraph">
-<p>A raid sends heroes to scout different rooms. If anyone falls, the
-raid retreats. <code>AsyncScope</code> binds child task lifetimes to a
scope:</p>
-</div>
-<div class="listingblock">
-<div class="content">
-<pre class="prettyprint highlight"><code data-lang="groovy">def
raidDungeon(List<Hero> party, List<Room> rooms) {
- AsyncScope.withScope { scope ->
- var missions = unique(party, rooms).collect { hero, room ->
- scope.async { hero.scout(room) }
- }
- missions.collect { await it } // all loot gathered
- }
-}</code></pre>
-</div>
-</div>
-<div class="paragraph">
-<p>By default, <code>AsyncScope</code> uses <strong>fail-fast</strong>
semantics: if any task
-fails, siblings are cancelled immediately. The scope guarantees all
-children have completed when <code>withScope</code> returns.</p>
-</div>
-<div class="sect3">
-<h4 id="_timeouts">Timeouts</h4>
-<div class="paragraph">
-<p>A raid with a time limit:</p>
-</div>
-<div class="listingblock">
-<div class="content">
-<pre class="prettyprint highlight"><code data-lang="groovy">def
raidWithTimeLimit(List<Hero> party, List<Room> rooms) {
- try {
- await Awaitable.orTimeoutMillis(
- async { raidDungeon(party, rooms) }, 30_000)
- } catch (TimeoutException e) {
- party.each { it.retreat() }
- return []
- }
-}</code></pre>
-</div>
-</div>
-<div class="paragraph">
-<p>Or with a fallback value:</p>
-</div>
-<div class="listingblock">
-<div class="content">
-<pre class="prettyprint highlight"><code data-lang="groovy">var loot = await
Awaitable.completeOnTimeoutMillis(
- async { raidDungeon(heroes, rooms) }, ['an old boot'], 30_000)</code></pre>
-</div>
-</div>
-</div>
-<div class="sect3">
-<h4 id="_complementing_jdk_structured_concurrency">Complementing JDK
structured concurrency</h4>
-<div class="paragraph">
-<p><code>AsyncScope</code> shares the same design goals as Java’s
-<code>StructuredTaskScope</code> but adds:</p>
-</div>
-<div class="ulist">
-<ul>
-<li>
-<p><strong><code>async</code>/<code>await</code> integration</strong> —
<code>scope.async { …​ }</code> and
-<code>await</code> instead of <code>fork()</code> + <code>join()</code>.</p>
-</li>
-<li>
-<p><strong>Works on JDK 17+</strong> — uses <code>ThreadLocal</code> (virtual
threads on 21+).</p>
-</li>
-<li>
-<p><strong>Composes with other features</strong> — <code>defer</code>,
<code>for await</code>, channels,
-and combinators all work inside a scope.</p>
-</li>
-<li>
-<p><strong>Groovy-idiomatic API</strong> — <code>AsyncScope.withScope { scope
→ … }</code>
-with a closure, no <code>try</code>-with-resources boilerplate.</p>
-</li>
-</ul>
-</div>
-</div>
-</div>
-<div class="sect2">
-<h3 id="_framework_adapters">Framework adapters</h3>
-<div class="paragraph">
-<p><code>await</code> natively understands <code>CompletableFuture</code>,
<code>CompletionStage</code>,
-<code>Future</code>, and any type with a registered
<code>AwaitableAdapter</code>.</p>
-</div>
-<div class="paragraph">
-<p>Drop-in adapter modules are provided:</p>
-</div>
-<div class="ulist">
-<ul>
-<li>
-<p><strong><code>groovy-reactor</code></strong> — <code>await</code> on
<code>Mono</code>, <code>for await</code> over <code>Flux</code></p>
-</li>
-<li>
-<p><strong><code>groovy-rxjava</code></strong> — <code>await</code> on
<code>Single</code>/<code>Maybe</code>/<code>Completable</code>,
-<code>for await</code> over <code>Observable</code>/<code>Flowable</code></p>
-</li>
-</ul>
-</div>
-<div class="paragraph">
-<p>Without the adapter:</p>
-</div>
-<div class="listingblock">
-<div class="content">
-<pre class="prettyprint highlight"><code data-lang="groovy">def result =
Single.just('hello').toCompletionStage().toCompletableFuture().join()</code></pre>
-</div>
-</div>
-<div class="paragraph">
-<p>With <code>groovy-rxjava</code> on the classpath:</p>
-</div>
-<div class="listingblock">
-<div class="content">
-<pre class="prettyprint highlight"><code data-lang="groovy">def result = await
Awaitable.from(Single.just('hello'))</code></pre>
-</div>
-</div>
-</div>
-</div>
-</div>
-<div class="sect1">
-<h2 id="_best_practices">Best practices</h2>
-<div class="sectionbody">
-<div class="sect2">
-<h3 id="_prefer_returning_values_over_shared_mutation">Prefer returning values
over shared mutation</h3>
-<div class="paragraph">
-<p>Async closures run on separate threads. Mutating shared variables
-is a race condition:</p>
-</div>
-<div class="listingblock">
-<div class="content">
-<pre class="prettyprint highlight"><code data-lang="groovy">// UNSAFE
-var count = 0
-def tasks = (1..100).collect { async { count++ } }
-tasks.each { await it }
-// count may not be 100!</code></pre>
-</div>
-</div>
-<div class="paragraph">
-<p>Return values and collect results instead:</p>
-</div>
-<div class="listingblock">
-<div class="content">
-<pre class="prettyprint highlight"><code data-lang="groovy">// SAFE
-def tasks = (1..100).collect { n -> async { n } }
-def results = await Awaitable.all(*tasks)
-assert results.sum() == 5050</code></pre>
-</div>
-</div>
-<div class="paragraph">
-<p>When shared mutable state is unavoidable, use the appropriate
-concurrency-aware type — <code>AtomicInteger</code> for a shared counter,
-or thread-safe types from <code>java.util.concurrent</code>.</p>
-</div>
-</div>
-<div class="sect2">
-<h3 id="_choosing_the_right_tool">Choosing the right tool</h3>
-<table class="tableblock frame-all grid-all stretch">
-<colgroup>
-<col style="width: 40%;">
-<col style="width: 60%;">
-</colgroup>
-<thead>
-<tr>
-<th class="tableblock halign-left valign-top">Feature</th>
-<th class="tableblock halign-left valign-top">Use when…​</th>
-</tr>
-</thead>
-<tbody>
-<tr>
-<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>async</code>/<code>await</code></p></td>
-<td class="tableblock halign-left valign-top"><p class="tableblock">Sequential
steps with I/O or blocking work.</p></td>
-</tr>
-<tr>
-<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>Awaitable.all</code> / <code>any</code> /
<code>first</code></p></td>
-<td class="tableblock halign-left valign-top"><p class="tableblock">Launch
independent tasks, collect all, race them, or take first success.</p></td>
-</tr>
-<tr>
-<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>yield return</code> / <code>for await</code></p></td>
-<td class="tableblock halign-left valign-top"><p class="tableblock">Producing
or consuming a stream of values.</p></td>
-</tr>
-<tr>
-<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>defer</code></p></td>
-<td class="tableblock halign-left valign-top"><p class="tableblock">Guaranteed
cleanup without nested <code>try</code>/<code>finally</code>.</p></td>
-</tr>
-<tr>
-<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>AsyncChannel</code></p></td>
-<td class="tableblock halign-left valign-top"><p
class="tableblock">Producer/consumer communication between tasks.</p></td>
-</tr>
-<tr>
-<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>AsyncScope</code></p></td>
-<td class="tableblock halign-left valign-top"><p class="tableblock">Child task
lifetimes tied to a scope with fail-fast cancellation.</p></td>
-</tr>
-<tr>
-<td class="tableblock halign-left valign-top"><p class="tableblock">Framework
adapters</p></td>
-<td class="tableblock halign-left valign-top"><p
class="tableblock">Transparent <code>await</code> / <code>for await</code> with
Reactor or RxJava types.</p></td>
-</tr>
-</tbody>
-</table>
-</div>
-</div>
-</div>
-<div class="sect1">
-<h2 id="_how_it_relates_to_gpars_and_virtual_threads">How it relates to GPars
and virtual threads</h2>
-<div class="sectionbody">
-<div class="paragraph">
-<p>Readers of the
-<a href="https://groovy.apache.org/blog/gpars-meets-virtual-threads">GPars
meets virtual threads</a>
-blog post will recall that GPars provides parallel collections,
-actors, agents, and dataflow concurrency.</p>
-</div>
-<div class="paragraph">
-<p>Async/await complements GPars rather than replacing it. GPars
-excels at data-parallel operations and actor-based designs.
-Async/await targets sequential-looking code that is actually
-asynchronous, with language-level support for streams, cleanup,
-structured concurrency, and framework bridging.</p>
-</div>
-<div class="paragraph">
-<p>Both approaches benefit from virtual threads on JDK 21+, and
-both can coexist in the same codebase.</p>
-</div>
-</div>
-</div>
-<div class="sect1">
-<h2 id="_conclusion">Conclusion</h2>
-<div class="sectionbody">
-<div class="paragraph">
-<p>Through our <em>Groovy Quest</em> examples we’ve seen how async/await
lets
-you write concurrent code that reads like synchronous code — from
-loading a hero’s quest, to preparing a battle in parallel, streaming
-dungeon waves, cleaning up summoned familiars, coordinating a boss
-fight over channels, and rallying a raid party with structured
-concurrency.</p>
-</div>
-<div class="paragraph">
-<p>The design philosophy is simple: closures run on real threads (virtual
-when available), stack traces are preserved, exceptions propagate
-naturally, and there’s no function coloring. The caller decides
what’s
-concurrent — not the method signature.</p>
-</div>
-</div>
-</div>
-<div class="sect1">
-<h2 id="_references">References</h2>
-<div class="sectionbody">
-<div class="ulist">
-<ul>
-<li>
-<p><a href="https://issues.apache.org/jira/browse/GROOVY-9381">GROOVY-9381 —
Tracking issue</a></p>
-</li>
-<li>
-<p><a href="https://openjdk.org/jeps/525">JEP 525 — Structured
Concurrency</a></p>
-</li>
-<li>
-<p><a href="https://groovy.apache.org/blog/gpars-meets-virtual-threads">GPars
meets Virtual Threads</a></p>
-</li>
-<li>
-<p><a href="http://gpars.org/">GPars</a></p>
-</li>
-</ul>
-</div>
-</div>
-</div></div></div></div></div><footer id='footer'>
- <div class='row'>
- <div class='colset-3-footer'>
- <div class='col-1'>
- <h1>Groovy</h1><ul>
- <li><a
href='https://groovy-lang.org/learn.html'>Learn</a></li><li><a
href='https://groovy-lang.org/documentation.html'>Documentation</a></li><li><a
href='/download.html'>Download</a></li><li><a
href='https://groovy-lang.org/support.html'>Support</a></li><li><a
href='/'>Contribute</a></li><li><a
href='https://groovy-lang.org/ecosystem.html'>Ecosystem</a></li><li><a
href='/blog'>Blog posts</a></li><li><a
href='https://groovy.apache.org/events.ht [...]
- </ul>
- </div><div class='col-2'>
- <h1>About</h1><ul>
- <li><a
href='https://github.com/apache/groovy'>Source code</a></li><li><a
href='https://groovy-lang.org/security.html'>Security</a></li><li><a
href='https://groovy-lang.org/learn.html#books'>Books</a></li><li><a
href='https://groovy-lang.org/thanks.html'>Thanks</a></li><li><a
href='http://www.apache.org/foundation/sponsorship.html'>Sponsorship</a></li><li><a
href='https://groovy-lang.org/faq.html'>FAQ</a></li><li><a
href='https://groovy-lang.or [...]
- </ul>
- </div><div class='col-3'>
- <h1>Socialize</h1><ul>
- <li><a
href='https://groovy-lang.org/mailing-lists.html'>Discuss on the mailing
list</a></li><li><a href='https://x.com/ApacheGroovy'>Groovy on
X</a></li><li><a href='https://bsky.app/profile/groovy.apache.org'>Groovy on
Bluesky</a></li><li><a href='https://fosstodon.org/@ApacheGroovy'>Groovy on
Mastodon</a></li><li><a
href='https://www.linkedin.com/company/106402668/admin/dashboard/'>Groovy on
LinkedIn</a></li><li><a href='https://groovy-lang. [...]
- </ul>
- </div><div class='col-right'>
- <p>
- The Groovy programming language is
supported by the <a href='https://www.apache.org'>Apache Software
Foundation</a> and the Groovy community.
- </p><div text-align='right'>
- <img
src='https://www.apache.org/img/asf_logo.png' title='The Apache Software
Foundation' alt='The Apache Software Foundation' style='width:60%'/>
- </div><p>Apache, Apache Groovy,
Groovy, and the ASF logo are either registered trademarks or trademarks of The
Apache Software Foundation.</p>
- </div>
- </div><div class='clearfix'>© 2003-2026
the Apache Groovy project — Groovy is Open Source: <a
href='https://www.apache.org/licenses/LICENSE-2.0.html' alt='Apache 2
License'>license</a>, <a
href='https://privacy.apache.org/policies/privacy-policy-public.html'>privacy
policy</a>.</div>
- </div>
- </footer></div>
- </div>
- </div>
- </div>
- </div><script src='../js/vendor/jquery-1.10.2.min.js'
defer></script><script src='../js/vendor/classie.js' defer></script><script
src='../js/vendor/bootstrap.js' defer></script><script
src='../js/vendor/sidebarEffects.js' defer></script><script
src='../js/vendor/modernizr-2.6.2.min.js' defer></script><script
src='../js/plugins.js' defer></script><script
src='../js/vendor/prettify.min.js'></script><script>document.addEventListener('DOMContentLoaded',prettyPrint)</script>
-</body></html>
\ No newline at end of file
diff --git a/blog/index.html b/blog/index.html
index 5a61e3b..383a81e 100644
--- a/blog/index.html
+++ b/blog/index.html
@@ -59,7 +59,7 @@
</ul>
</div>
</div>
- </div><div id='content' class='page-1'><div
class='row'><div class='row-fluid'><div class='col-lg-3' id='blog-index'><ul
class='nav-sidebar list'><li class='active'><a
href='/blog/'>Blogs</a></li><li><a href='groovy-null-checker'>Compile-time null
safety for Groovy™</a></li><li><a href='groovy-async-await'>Async/await
for Groovy™</a></li><li><a href='loop-invariants'>Design by contract with
Groovy™: loop invariants</a></li><li><a href='groovy-gra [...]
+ </div><div id='content' class='page-1'><div
class='row'><div class='row-fluid'><div class='col-lg-3' id='blog-index'><ul
class='nav-sidebar list'><li class='active'><a
href='/blog/'>Blogs</a></li><li><a href='groovy-async-await'>Async/await for
Groovy™</a></li><li><a href='groovy-null-checker'>Compile-time null
safety for Groovy™</a></li><li><a href='loop-invariants'>Design by
contract with Groovy™: loop invariants</a></li><li><a href='groovy-gra
[...]
<div class='row'>
<div class='colset-3-footer'>
<div class='col-1'>
diff --git a/search/search-index.json b/search/search-index.json
index 77d855f..b0023da 100644
--- a/search/search-index.json
+++ b/search/search-index.json
@@ -261,7 +261,7 @@
{
"id": "blog/groovy-async-await.html",
"title": "The Apache Groovy programming language - Blogs - Async/await
for Groovy™",
- "content": "The Apache Groovy programming language - Blogs -
Async/await for Groovy™ Socialize Discuss on the mailing list Groovy on X
Groovy on Bluesky Groovy on Mastodon Groovy on LinkedIn Events and conferences
Source code on GitHub Report issues in Jira Stack Overflow questions Slack
Community You are using an outdated browser. Please upgrade your browser to
improve your experience. Apache Groovy™ Learn Documentation Download
Support Contribute Ecosystem Blog post [...]
+ "content": "The Apache Groovy programming language - Blogs -
Async/await for Groovy™ Socialize Discuss on the mailing list Groovy on X
Groovy on Bluesky Groovy on Mastodon Groovy on LinkedIn Events and conferences
Source code on GitHub Report issues in Jira Stack Overflow questions Slack
Community You are using an outdated browser. Please upgrade your browser to
improve your experience. Apache Groovy™ Learn Documentation Download
Support Contribute Ecosystem Blog post [...]
"url": "blog/groovy-async-await.html",
"site": "dev"
},
@@ -349,13 +349,6 @@
"url": "blog/groovy-haiku-processing.html",
"site": "dev"
},
- {
- "id": "blog/groovy-async-await_5.html",
- "title": "The Apache Groovy programming language - Blogs - Async/await
for Groovy™",
- "content": "The Apache Groovy programming language - Blogs -
Async/await for Groovy™ Socialize Discuss on the mailing list Groovy on X
Groovy on Bluesky Groovy on Mastodon Groovy on LinkedIn Events and conferences
Source code on GitHub Report issues in Jira Stack Overflow questions Slack
Community You are using an outdated browser. Please upgrade your browser to
improve your experience. Apache Groovy™ Learn Documentation Download
Support Contribute Ecosystem Blog post [...]
- "url": "blog/groovy-async-await_5.html",
- "site": "dev"
- },
{
"id": "blog/groovy-ai.html",
"title": "The Apache Groovy programming language - Blogs - Exploring
AI with Groovy™",
@@ -702,7 +695,7 @@
{
"id": "blog/index.html",
"title": "The Apache Groovy programming language - Blogs",
- "content": "The Apache Groovy programming language - Blogs Socialize
Discuss on the mailing list Groovy on X Groovy on Bluesky Groovy on Mastodon
Groovy on LinkedIn Events and conferences Source code on GitHub Report issues
in Jira Stack Overflow questions Slack Community You are using an outdated
browser. Please upgrade your browser to improve your experience. Apache
Groovy™ Learn Documentation Download Support Contribute Ecosystem Blog
posts Socialize Blogs Compile-time n [...]
+ "content": "The Apache Groovy programming language - Blogs Socialize
Discuss on the mailing list Groovy on X Groovy on Bluesky Groovy on Mastodon
Groovy on LinkedIn Events and conferences Source code on GitHub Report issues
in Jira Stack Overflow questions Slack Community You are using an outdated
browser. Please upgrade your browser to improve your experience. Apache
Groovy™ Learn Documentation Download Support Contribute Ecosystem Blog
posts Socialize Blogs Async/await fo [...]
"url": "blog/index.html",
"site": "dev"
},