You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by su...@apache.org on 2026/02/28 19:42:38 UTC

(groovy) 01/01: GROOVY-9381: Support async/await like ES7

This is an automated email from the ASF dual-hosted git repository.

sunlan pushed a commit to branch GROOVY-9381_2
in repository https://gitbox.apache.org/repos/asf/groovy.git

commit 86c39238448bc988764706847cb4be57897fd8dc
Author: Daniel Sun <su...@apache.org>
AuthorDate: Sun Mar 1 04:35:02 2026 +0900

    GROOVY-9381: Support async/await like ES7
---
 build.gradle                                       |    2 +
 gradle/verification-metadata.xml                   |   16 +
 src/antlr/GroovyLexer.g4                           |    2 +
 src/antlr/GroovyParser.g4                          |   14 +-
 src/main/java/groovy/concurrent/AsyncStream.java   |   67 +
 .../groovy/concurrent/AsyncStreamGenerator.java    |  117 ++
 src/main/java/groovy/concurrent/AwaitResult.java   |  101 +
 src/main/java/groovy/concurrent/Awaitable.java     |  122 ++
 .../java/groovy/concurrent/AwaitableAdapter.java   |   76 +
 .../concurrent/AwaitableAdapterRegistry.java       |  181 ++
 src/main/java/groovy/concurrent/GroovyPromise.java |  127 ++
 src/main/java/groovy/transform/Async.java          |  121 ++
 src/main/java/groovy/transform/AsyncUtils.java     |  524 +++++
 .../apache/groovy/parser/antlr4/AstBuilder.java    |  152 +-
 .../java/org/codehaus/groovy/ast/ModifierNode.java |    2 +
 .../groovy/transform/AsyncASTTransformation.java   |  306 +++
 .../groovy/transform/AsyncAwaitSyntaxTest.groovy   | 2108 ++++++++++++++++++++
 .../groovy/transform/AsyncClosureLambdaTest.groovy |  793 ++++++++
 .../transform/AsyncExceptionHandlingTest.groovy    | 1161 +++++++++++
 .../transform/AsyncFrameworkIntegrationTest.groovy |  506 +++++
 .../groovy/transform/AsyncTransformTest.groovy     | 1401 +++++++++++++
 .../groovy/transform/AsyncVirtualThreadTest.groovy | 1115 +++++++++++
 versions.properties                                |    2 +
 23 files changed, 9013 insertions(+), 3 deletions(-)

diff --git a/build.gradle b/build.gradle
index ed3f22feaa..5fe1efbfb1 100644
--- a/build.gradle
+++ b/build.gradle
@@ -122,6 +122,8 @@ dependencies {
     testImplementation "com.thoughtworks.qdox:qdox:${versions.qdox}"
     testImplementation "com.fasterxml.jackson.core:jackson-databind:${versions.jackson}"
     testImplementation "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:${versions.jackson}"
+    testImplementation "io.reactivex.rxjava3:rxjava:${versions.rxjava3}"
+    testImplementation "io.projectreactor:reactor-core:${versions.reactor}"
 
     testFixturesImplementation projects.groovyXml
     testFixturesImplementation projects.groovyTest
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index 3ec4ebf779..340c7c52be 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -114,6 +114,7 @@
          <ignored-key id="C71FB765CD9DE313" reason="Key couldn't be downloaded from any key server"/>
          <ignored-key id="C7CA19B7B620D787" reason="Key couldn't be downloaded from any key server"/>
          <ignored-key id="CA80D1F0EB6CA4BA" reason="Key couldn't be downloaded from any key server"/>
+         <ignored-key id="D1031D14464180E0" reason="Key couldn't be downloaded from any key server"/>
          <ignored-key id="D2151178A123C97F" reason="Key couldn't be downloaded from any key server"/>
          <ignored-key id="D364ABAA39A47320" reason="Key couldn't be downloaded from any key server"/>
          <ignored-key id="D7742D58455ECC7C" reason="Key couldn't be downloaded from any key server"/>
@@ -851,6 +852,16 @@
             <sha512 value="f220e44fe6b61f8dbb61226f832dfb16a09584384540fd48a4dff5c4de9fee060623f85cbead720dfe776aa25105949e70758a9bb1d9db43f63068d8d22164c9" origin="Generated by Gradle"/>
          </artifact>
       </component>
+      <component group="io.projectreactor" name="reactor-core" version="3.7.3">
+         <artifact name="reactor-core-3.7.3.jar">
+            <pgp value="48B086A7D843CFA258E83286928FBF39003C0425"/>
+         </artifact>
+      </component>
+      <component group="io.reactivex.rxjava3" name="rxjava" version="3.1.10">
+         <artifact name="rxjava-3.1.10.jar">
+            <pgp value="E9CC3CD1AE59E851E4DB3FA350FFD7487D34B5B9"/>
+         </artifact>
+      </component>
       <component group="jakarta.activation" name="jakarta.activation-api" version="1.2.1">
          <artifact name="jakarta.activation-api-1.2.1.jar">
             <pgp value="6DD3B8C64EF75253BEB2C53AD908A43FB7EC07AC"/>
@@ -2206,6 +2217,11 @@
             <sha512 value="adcc480f68828ffd68d03846be852988b595c2e1bb69224d273578dd6c2ad2773edfe96625a7c00bc40ae0f2d1cac8412eaa54b88cc8e681b0b4c0ee3b082333" origin="Generated by Gradle"/>
          </artifact>
       </component>
+      <component group="org.reactivestreams" name="reactive-streams" version="1.0.4">
+         <artifact name="reactive-streams-1.0.4.jar">
+            <sha512 value="cdab6bd156f39106cd6bbfd47df1f4b0a89dc4aa28c68c31ef12a463193c688897e415f01b8d7f0d487b0e6b5bd2f19044bf8605704b024f26d6aa1f4f9a2471" origin="Generated by Gradle" reason="A key couldn't be downloaded"/>
+         </artifact>
+      </component>
       <component group="org.reflections" name="reflections" version="0.10.2">
          <artifact name="reflections-0.10.2.jar">
             <pgp value="3F2A008A91D11A7FAC4A0786F13D3E721D56BD54"/>
diff --git a/src/antlr/GroovyLexer.g4 b/src/antlr/GroovyLexer.g4
index 79ddf78dbd..9846b309bf 100644
--- a/src/antlr/GroovyLexer.g4
+++ b/src/antlr/GroovyLexer.g4
@@ -392,6 +392,8 @@ DEF             : 'def';
 IN              : 'in';
 TRAIT           : 'trait';
 THREADSAFE      : 'threadsafe'; // reserved keyword
+ASYNC           : 'async';
+AWAIT           : 'await';
 
 // §3.9 Keywords
 BuiltInPrimitiveType
diff --git a/src/antlr/GroovyParser.g4 b/src/antlr/GroovyParser.g4
index 88e3b8b713..7becfb6030 100644
--- a/src/antlr/GroovyParser.g4
+++ b/src/antlr/GroovyParser.g4
@@ -134,6 +134,7 @@ modifier
           |   VOLATILE
           |   DEF
           |   VAR
+          |   ASYNC
           )
     ;
 
@@ -600,7 +601,7 @@ switchStatement
     ;
 
 loopStatement
-    :   FOR LPAREN forControl RPAREN nls statement                                                            #forStmtAlt
+    :   FOR AWAIT? LPAREN forControl RPAREN nls statement                                                      #forStmtAlt
     |   WHILE expressionInPar nls statement                                                                   #whileStmtAlt
     |   DO nls statement nls WHILE expressionInPar                                                            #doWhileStmtAlt
     ;
@@ -642,6 +643,7 @@ statement
     |   continueStatement                                                                                   #continueStmtAlt
     |   { inSwitchExpressionLevel > 0 }?
         yieldStatement                                                                                      #yieldStmtAlt
+    |   YIELD RETURN nls expression                                                                         #yieldReturnStmtAlt
     |   identifier COLON nls statement                                                                      #labeledStmtAlt
     |   assertStatement                                                                                     #assertStmtAlt
     |   localVariableDeclaration                                                                            #localVariableDeclarationStmtAlt
@@ -778,13 +780,17 @@ expression
     // must come before postfixExpression to resolve the ambiguities between casting and call on parentheses expression, e.g. (int)(1 / 2)
     :   castParExpression castOperandExpression                                             #castExprAlt
 
+    // async closure/lambda must come before postfixExpression to resolve the ambiguities between async and method call, e.g. async { ... }
+    |   ASYNC nls closureOrLambdaExpression                                                 #asyncClosureExprAlt
+
     // qualified names, array expressions, method invocation, post inc/dec
     |   postfixExpression                                                                   #postfixExprAlt
 
     |   switchExpression                                                                    #switchExprAlt
 
-    // ~(BNOT)/!(LNOT) (level 1)
+    // ~(BNOT)/!(LNOT)/await (level 1)
     |   (BITNOT | NOT) nls expression                                                       #unaryNotExprAlt
+    |   AWAIT nls expression                                                                #awaitExprAlt
 
     // math power operator (**) (level 2)
     |   left=expression op=POWER nls right=expression                                       #powerExprAlt
@@ -1228,6 +1234,8 @@ identifier
     :   Identifier
     |   CapitalizedIdentifier
     |   AS
+    |   ASYNC
+    |   AWAIT
     |   IN
     |   PERMITS
     |   RECORD
@@ -1246,6 +1254,8 @@ keywords
     :   ABSTRACT
     |   AS
     |   ASSERT
+    |   ASYNC
+    |   AWAIT
     |   BREAK
     |   CASE
     |   CATCH
diff --git a/src/main/java/groovy/concurrent/AsyncStream.java b/src/main/java/groovy/concurrent/AsyncStream.java
new file mode 100644
index 0000000000..6e83cc96d4
--- /dev/null
+++ b/src/main/java/groovy/concurrent/AsyncStream.java
@@ -0,0 +1,67 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package groovy.concurrent;
+
+/**
+ * Asynchronous iteration abstraction, analogous to C#'s
+ * {@code IAsyncEnumerable<T>} or JavaScript's async iterables.
+ * <p>
+ * Used with the {@code for await} syntax:
+ * <pre>
+ * for await (item in asyncStream) {
+ *     process(item)
+ * }
+ * </pre>
+ * <p>
+ * Third-party reactive types (Reactor {@code Flux}, RxJava {@code Observable})
+ * can be adapted to {@code AsyncStream} via {@link AwaitableAdapter}.
+ *
+ * @param <T> the element type
+ * @see AwaitableAdapter
+ * @since 6.0.0
+ */
+public interface AsyncStream<T> {
+
+    /**
+     * Asynchronously advances to the next element. Returns an {@link Awaitable}
+     * that completes with {@code true} if an element is available, or
+     * {@code false} if the stream is exhausted.
+     */
+    Awaitable<Boolean> moveNext();
+
+    /**
+     * Returns the current element. Must only be called after {@link #moveNext()}
+     * has completed with {@code true}.
+     */
+    T getCurrent();
+
+    /**
+     * Returns an empty {@code AsyncStream} that completes immediately.
+     */
+    @SuppressWarnings("unchecked")
+    static <T> AsyncStream<T> empty() {
+        return (AsyncStream<T>) EMPTY;
+    }
+
+    /** Singleton empty stream instance. */
+    AsyncStream<Object> EMPTY = new AsyncStream<>() {
+        @Override public Awaitable<Boolean> moveNext() { return Awaitable.of(false); }
+        @Override public Object getCurrent() { return null; }
+    };
+}
diff --git a/src/main/java/groovy/concurrent/AsyncStreamGenerator.java b/src/main/java/groovy/concurrent/AsyncStreamGenerator.java
new file mode 100644
index 0000000000..17a19c975d
--- /dev/null
+++ b/src/main/java/groovy/concurrent/AsyncStreamGenerator.java
@@ -0,0 +1,117 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package groovy.concurrent;
+
+import java.util.concurrent.SynchronousQueue;
+
+/**
+ * A producer/consumer implementation of {@link AsyncStream} used by
+ * {@code async} methods that contain {@code yield return} statements.
+ * <p>
+ * The producer (method body) runs on a separate thread and calls
+ * {@link #yield(Object)} for each emitted element. The consumer
+ * calls {@link #moveNext()}/{@link #getCurrent()} — typically via
+ * a {@code for await} loop.
+ * <p>
+ * Uses a {@link SynchronousQueue} to provide natural back-pressure:
+ * the producer thread blocks at each {@code yield return} until the
+ * consumer has consumed the previous element (mirroring C#'s async
+ * iterator suspension semantics).
+ *
+ * @param <T> the element type
+ * @since 6.0.0
+ */
+public class AsyncStreamGenerator<T> implements AsyncStream<T> {
+
+    private static final Object DONE = new Object();
+
+    private final SynchronousQueue<Object> queue = new SynchronousQueue<>();
+    private T current;
+
+    /**
+     * Produces the next element. Called from the generator body when
+     * a {@code yield return expr} statement is executed. Blocks until
+     * the consumer is ready.
+     */
+    public void yield(Object value) {
+        try {
+            queue.put(new Item(value));
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new java.util.concurrent.CancellationException("Interrupted during yield");
+        }
+    }
+
+    /**
+     * Signals that the generator has completed (no more elements).
+     */
+    public void complete() {
+        try {
+            queue.put(DONE);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+        }
+    }
+
+    /**
+     * Signals that the generator failed with an exception.
+     */
+    public void error(Throwable t) {
+        try {
+            queue.put(new ErrorItem(t));
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+        }
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public Awaitable<Boolean> moveNext() {
+        try {
+            Object next = queue.take();
+            if (next == DONE) {
+                return Awaitable.of(false);
+            }
+            if (next instanceof ErrorItem ei) {
+                Throwable cause = ei.error;
+                if (cause instanceof Error err) throw err;
+                throw sneakyThrow(cause);
+            }
+            current = (T) ((Item) next).value;
+            return Awaitable.of(true);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new java.util.concurrent.CancellationException("Interrupted during moveNext");
+        }
+    }
+
+    @Override
+    public T getCurrent() {
+        return current;
+    }
+
+    // Wrapper to handle null values in the queue
+    private record Item(Object value) { }
+    private record ErrorItem(Throwable error) { }
+
+    @SuppressWarnings("unchecked")
+    private static <T extends Throwable> RuntimeException sneakyThrow(Throwable t) throws T {
+        throw (T) t;
+    }
+}
diff --git a/src/main/java/groovy/concurrent/AwaitResult.java b/src/main/java/groovy/concurrent/AwaitResult.java
new file mode 100644
index 0000000000..52e77d2342
--- /dev/null
+++ b/src/main/java/groovy/concurrent/AwaitResult.java
@@ -0,0 +1,101 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package groovy.concurrent;
+
+import java.util.Objects;
+import java.util.function.Function;
+
+/**
+ * Represents the outcome of an asynchronous computation that may have
+ * succeeded or failed. This is used by {@code awaitAllSettled()} —
+ * the Groovy equivalent of JavaScript's {@code Promise.allSettled()}.
+ * <p>
+ * An {@code AwaitResult} is either a {@linkplain #isSuccess() success}
+ * carrying a value, or a {@linkplain #isFailure() failure} carrying a
+ * {@link Throwable}.
+ *
+ * @param <T> the value type
+ * @since 6.0.0
+ */
+public final class AwaitResult<T> {
+
+    private final T value;
+    private final Throwable error;
+    private final boolean success;
+
+    private AwaitResult(T value, Throwable error, boolean success) {
+        this.value = value;
+        this.error = error;
+        this.success = success;
+    }
+
+    /**
+     * Creates a successful result with the given value.
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> AwaitResult<T> success(Object value) {
+        return new AwaitResult<>((T) value, null, true);
+    }
+
+    /**
+     * Creates a failure result with the given exception.
+     */
+    public static <T> AwaitResult<T> failure(Throwable error) {
+        return new AwaitResult<>(null, Objects.requireNonNull(error), false);
+    }
+
+    public boolean isSuccess() {
+        return success;
+    }
+
+    public boolean isFailure() {
+        return !success;
+    }
+
+    /**
+     * Returns the value if successful; throws {@link IllegalStateException} if failed.
+     */
+    public T getValue() {
+        if (!success) throw new IllegalStateException("Cannot get value from a failed result");
+        return value;
+    }
+
+    /**
+     * Returns the exception if failed; throws {@link IllegalStateException} if successful.
+     */
+    public Throwable getError() {
+        if (success) throw new IllegalStateException("Cannot get error from a successful result");
+        return error;
+    }
+
+    /**
+     * Returns the value if successful, or applies the given function to
+     * the error to produce a fallback value.
+     */
+    public T getOrElse(Function<Throwable, ? extends T> fallback) {
+        return success ? value : fallback.apply(error);
+    }
+
+    @Override
+    public String toString() {
+        return success
+            ? "AwaitResult.Success[" + value + "]"
+            : "AwaitResult.Failure[" + error + "]";
+    }
+}
diff --git a/src/main/java/groovy/concurrent/Awaitable.java b/src/main/java/groovy/concurrent/Awaitable.java
new file mode 100644
index 0000000000..c429ae2cdd
--- /dev/null
+++ b/src/main/java/groovy/concurrent/Awaitable.java
@@ -0,0 +1,122 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package groovy.concurrent;
+
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Function;
+
+/**
+ * Core abstraction for asynchronous computations in Groovy, decoupled from
+ * any specific JDK or third-party async API. Analogous to C#'s {@code Task<T>},
+ * JavaScript's {@code Promise<T>}.
+ * <p>
+ * {@code Awaitable} is the return type of {@code async} methods and the input
+ * type for the {@code await} expression. Third-party frameworks (RxJava, Reactor,
+ * etc.) can integrate by registering an {@link AwaitableAdapter} via
+ * {@link AwaitableAdapterRegistry}.
+ * <p>
+ * The default implementation, {@link GroovyPromise}, delegates to
+ * {@link CompletableFuture} but users never need to depend on that detail.
+ *
+ * @param <T> the result type
+ * @see GroovyPromise
+ * @see AwaitableAdapter
+ * @since 6.0.0
+ */
+public interface Awaitable<T> {
+
+    /**
+     * Blocks until the computation completes and returns the result.
+     */
+    T get() throws InterruptedException, ExecutionException;
+
+    /**
+     * Blocks until the computation completes or the timeout expires.
+     */
+    T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
+
+    /**
+     * Returns {@code true} if the computation has completed (normally or exceptionally).
+     */
+    boolean isDone();
+
+    /**
+     * Attempts to cancel the computation. If the computation has not yet started
+     * or is still running, it will be cancelled with a {@link CancellationException}.
+     * Analogous to C#'s {@code CancellationToken}.
+     *
+     * @return {@code true} if the computation was successfully cancelled
+     */
+    boolean cancel();
+
+    /**
+     * Returns {@code true} if the computation was cancelled before completing normally.
+     */
+    boolean isCancelled();
+
+    /**
+     * Returns {@code true} if this computation completed exceptionally
+     * (including cancellation).
+     */
+    boolean isCompletedExceptionally();
+
+    /**
+     * Returns a new {@code Awaitable} whose result is obtained by applying the
+     * given function to this awaitable's result when it completes.
+     */
+    <U> Awaitable<U> then(Function<? super T, ? extends U> fn);
+
+    /**
+     * Returns a new {@code Awaitable} produced by applying the given function
+     * to this awaitable's result, flattening the nested {@code Awaitable}.
+     */
+    <U> Awaitable<U> thenCompose(Function<? super T, ? extends Awaitable<U>> fn);
+
+    /**
+     * Returns a new {@code Awaitable} that, if this one completes exceptionally,
+     * applies the given function to the exception to produce a recovery value.
+     */
+    Awaitable<T> exceptionally(Function<Throwable, ? extends T> fn);
+
+    /**
+     * Converts this {@code Awaitable} to a JDK {@link CompletableFuture}
+     * for interoperability with APIs that require it.
+     */
+    CompletableFuture<T> toCompletableFuture();
+
+    // ---- Static factories ----
+
+    /**
+     * Returns an already-completed {@code Awaitable} with the given value.
+     */
+    static <T> Awaitable<T> of(T value) {
+        return new GroovyPromise<>(CompletableFuture.completedFuture(value));
+    }
+
+    /**
+     * Returns an already-failed {@code Awaitable} with the given exception.
+     */
+    static <T> Awaitable<T> failed(Throwable error) {
+        return new GroovyPromise<>(CompletableFuture.failedFuture(error));
+    }
+}
diff --git a/src/main/java/groovy/concurrent/AwaitableAdapter.java b/src/main/java/groovy/concurrent/AwaitableAdapter.java
new file mode 100644
index 0000000000..5a1d52da54
--- /dev/null
+++ b/src/main/java/groovy/concurrent/AwaitableAdapter.java
@@ -0,0 +1,76 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package groovy.concurrent;
+
+/**
+ * Service Provider Interface (SPI) for adapting third-party asynchronous types
+ * to Groovy's {@link Awaitable} and {@link AsyncStream} abstractions.
+ * <p>
+ * Implementations are discovered automatically via {@link java.util.ServiceLoader}.
+ * To register an adapter, create a file
+ * {@code META-INF/services/groovy.concurrent.AwaitableAdapter} containing the
+ * fully-qualified class name of your implementation.
+ * <p>
+ * <b>Example – RxJava adapter:</b>
+ * <pre>
+ * public class RxJavaAwaitableAdapter implements AwaitableAdapter {
+ *     public boolean supportsAwaitable(Class&lt;?&gt; type) {
+ *         return io.reactivex.rxjava3.core.Single.class.isAssignableFrom(type);
+ *     }
+ *     public &lt;T&gt; Awaitable&lt;T&gt; toAwaitable(Object source) {
+ *         Single&lt;T&gt; single = (Single&lt;T&gt;) source;
+ *         return new GroovyPromise&lt;&gt;(single.toCompletionStage().toCompletableFuture());
+ *     }
+ *     // ... supportsAsyncStream / toAsyncStream for Observable, Flowable, etc.
+ * }
+ * </pre>
+ *
+ * @see AwaitableAdapterRegistry
+ * @since 6.0.0
+ */
+public interface AwaitableAdapter {
+
+    /**
+     * Returns {@code true} if this adapter can convert instances of the given
+     * type to {@link Awaitable}.
+     */
+    boolean supportsAwaitable(Class<?> type);
+
+    /**
+     * Converts the given source object to an {@link Awaitable}.
+     * Called only when {@link #supportsAwaitable} returned {@code true}.
+     */
+    <T> Awaitable<T> toAwaitable(Object source);
+
+    /**
+     * Returns {@code true} if this adapter can convert instances of the given
+     * type to {@link AsyncStream}.
+     */
+    default boolean supportsAsyncStream(Class<?> type) {
+        return false;
+    }
+
+    /**
+     * Converts the given source object to an {@link AsyncStream}.
+     * Called only when {@link #supportsAsyncStream} returned {@code true}.
+     */
+    default <T> AsyncStream<T> toAsyncStream(Object source) {
+        throw new UnsupportedOperationException("AsyncStream conversion not supported by " + getClass().getName());
+    }
+}
diff --git a/src/main/java/groovy/concurrent/AwaitableAdapterRegistry.java b/src/main/java/groovy/concurrent/AwaitableAdapterRegistry.java
new file mode 100644
index 0000000000..16968853b5
--- /dev/null
+++ b/src/main/java/groovy/concurrent/AwaitableAdapterRegistry.java
@@ -0,0 +1,181 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package groovy.concurrent;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.ServiceLoader;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Future;
+
+/**
+ * Central registry for {@link AwaitableAdapter} instances.
+ * <p>
+ * On class-load, adapters are discovered via {@link ServiceLoader} from
+ * {@code META-INF/services/groovy.concurrent.AwaitableAdapter}. A built-in
+ * adapter for {@link CompletableFuture} and {@link Future} is always present
+ * as the lowest-priority fallback.
+ * <p>
+ * Additional adapters can be registered at runtime via {@link #register}.
+ *
+ * @see AwaitableAdapter
+ * @since 6.0.0
+ */
+public class AwaitableAdapterRegistry {
+
+    private static final List<AwaitableAdapter> ADAPTERS = new CopyOnWriteArrayList<>();
+
+    static {
+        // SPI-discovered adapters
+        for (AwaitableAdapter adapter : ServiceLoader.load(AwaitableAdapter.class)) {
+            ADAPTERS.add(adapter);
+        }
+        // Built-in fallback (lowest priority)
+        ADAPTERS.add(new BuiltInAdapter());
+    }
+
+    private AwaitableAdapterRegistry() { }
+
+    /**
+     * Registers an adapter with higher priority than existing ones.
+     */
+    public static void register(AwaitableAdapter adapter) {
+        ADAPTERS.add(0, adapter);
+    }
+
+    /**
+     * Converts the given source to an {@link Awaitable}.
+     * If the source is already an {@code Awaitable}, it is returned as-is.
+     *
+     * @throws IllegalArgumentException if no adapter supports the source type
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> Awaitable<T> toAwaitable(Object source) {
+        if (source instanceof Awaitable) return (Awaitable<T>) source;
+        Class<?> type = source.getClass();
+        for (AwaitableAdapter adapter : ADAPTERS) {
+            if (adapter.supportsAwaitable(type)) {
+                return adapter.toAwaitable(source);
+            }
+        }
+        throw new IllegalArgumentException(
+                "No AwaitableAdapter found for type: " + type.getName()
+                        + ". Register one via AwaitableAdapterRegistry.register() or ServiceLoader.");
+    }
+
+    /**
+     * Converts the given source to an {@link AsyncStream}.
+     * If the source is already an {@code AsyncStream}, it is returned as-is.
+     *
+     * @throws IllegalArgumentException if no adapter supports the source type
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> AsyncStream<T> toAsyncStream(Object source) {
+        if (source instanceof AsyncStream) return (AsyncStream<T>) source;
+        Class<?> type = source.getClass();
+        for (AwaitableAdapter adapter : ADAPTERS) {
+            if (adapter.supportsAsyncStream(type)) {
+                return adapter.toAsyncStream(source);
+            }
+        }
+        throw new IllegalArgumentException(
+                "No AsyncStream adapter found for type: " + type.getName()
+                        + ". Register one via AwaitableAdapterRegistry.register() or ServiceLoader.");
+    }
+
+    /**
+     * Built-in adapter handling JDK {@link CompletableFuture}, {@link CompletionStage},
+     * {@link Future}, and {@link Iterable}/{@link Iterator} (for async stream bridging).
+     * <p>
+     * {@link CompletionStage} support enables seamless integration with frameworks
+     * that return {@code CompletionStage} (e.g., Spring's async APIs, Reactor's
+     * {@code Mono.toFuture()}, etc.) without any additional adapter registration.
+     */
+    private static class BuiltInAdapter implements AwaitableAdapter {
+
+        @Override
+        public boolean supportsAwaitable(Class<?> type) {
+            return CompletionStage.class.isAssignableFrom(type)
+                    || Future.class.isAssignableFrom(type);
+        }
+
+        @Override
+        @SuppressWarnings("unchecked")
+        public <T> Awaitable<T> toAwaitable(Object source) {
+            if (source instanceof CompletionStage) {
+                return new GroovyPromise<>(((CompletionStage<T>) source).toCompletableFuture());
+            }
+            if (source instanceof Future) {
+                Future<T> future = (Future<T>) source;
+                CompletableFuture<T> cf = new CompletableFuture<>();
+                if (future.isDone()) {
+                    completeFrom(cf, future);
+                } else {
+                    CompletableFuture.runAsync(() -> completeFrom(cf, future));
+                }
+                return new GroovyPromise<>(cf);
+            }
+            throw new IllegalArgumentException("Cannot convert to Awaitable: " + source.getClass());
+        }
+
+        @Override
+        public boolean supportsAsyncStream(Class<?> type) {
+            return Iterable.class.isAssignableFrom(type)
+                    || Iterator.class.isAssignableFrom(type);
+        }
+
+        @Override
+        @SuppressWarnings("unchecked")
+        public <T> AsyncStream<T> toAsyncStream(Object source) {
+            final Iterator<T> iterator;
+            if (source instanceof Iterable) {
+                iterator = ((Iterable<T>) source).iterator();
+            } else if (source instanceof Iterator) {
+                iterator = (Iterator<T>) source;
+            } else {
+                throw new IllegalArgumentException("Cannot convert to AsyncStream: " + source.getClass());
+            }
+            return new AsyncStream<T>() {
+                private T current;
+
+                @Override
+                public Awaitable<Boolean> moveNext() {
+                    boolean hasNext = iterator.hasNext();
+                    if (hasNext) current = iterator.next();
+                    return Awaitable.of(hasNext);
+                }
+
+                @Override
+                public T getCurrent() {
+                    return current;
+                }
+            };
+        }
+
+        private static <T> void completeFrom(CompletableFuture<T> cf, Future<T> future) {
+            try {
+                cf.complete(future.get());
+            } catch (Exception e) {
+                cf.completeExceptionally(e.getCause() != null ? e.getCause() : e);
+            }
+        }
+    }
+}
diff --git a/src/main/java/groovy/concurrent/GroovyPromise.java b/src/main/java/groovy/concurrent/GroovyPromise.java
new file mode 100644
index 0000000000..ba96003ef3
--- /dev/null
+++ b/src/main/java/groovy/concurrent/GroovyPromise.java
@@ -0,0 +1,127 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package groovy.concurrent;
+
+import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Function;
+
+/**
+ * Default {@link Awaitable} implementation backed by a {@link CompletableFuture}.
+ * <p>
+ * This is the concrete type returned by {@code async} methods.  It delegates
+ * all operations to an underlying {@code CompletableFuture} while keeping the
+ * public API limited to the {@code Awaitable} contract, thereby decoupling
+ * user code from JDK-specific async APIs.
+ *
+ * @param <T> the result type
+ * @see Awaitable
+ * @since 6.0.0
+ */
+public class GroovyPromise<T> implements Awaitable<T> {
+
+    private final CompletableFuture<T> future;
+
+    public GroovyPromise(CompletableFuture<T> future) {
+        this.future = Objects.requireNonNull(future, "future must not be null");
+    }
+
+    /**
+     * Creates a {@code GroovyPromise} wrapping the given {@link CompletableFuture}.
+     */
+    public static <T> GroovyPromise<T> of(CompletableFuture<T> future) {
+        return new GroovyPromise<>(future);
+    }
+
+    @Override
+    public T get() throws InterruptedException, ExecutionException {
+        return future.get();
+    }
+
+    @Override
+    public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
+        return future.get(timeout, unit);
+    }
+
+    @Override
+    public boolean isDone() {
+        return future.isDone();
+    }
+
+    @Override
+    public boolean cancel() {
+        return future.cancel(true);
+    }
+
+    @Override
+    public boolean isCancelled() {
+        return future.isCancelled();
+    }
+
+    @Override
+    public boolean isCompletedExceptionally() {
+        return future.isCompletedExceptionally();
+    }
+
+    @Override
+    public <U> Awaitable<U> then(Function<? super T, ? extends U> fn) {
+        return new GroovyPromise<>(future.thenApply(fn));
+    }
+
+    @Override
+    public <U> Awaitable<U> thenCompose(Function<? super T, ? extends Awaitable<U>> fn) {
+        return new GroovyPromise<>(future.thenCompose(t -> fn.apply(t).toCompletableFuture()));
+    }
+
+    @Override
+    public Awaitable<T> exceptionally(Function<Throwable, ? extends T> fn) {
+        return new GroovyPromise<>(future.exceptionally(t -> {
+            // Unwrap all wrapper layers so handler sees the original exception
+            Throwable cause = t;
+            while (cause.getCause() != null
+                    && (cause instanceof CompletionException
+                        || cause instanceof ExecutionException
+                        || cause instanceof java.lang.reflect.UndeclaredThrowableException
+                        || cause instanceof java.lang.reflect.InvocationTargetException)) {
+                cause = cause.getCause();
+            }
+            return fn.apply(cause);
+        }));
+    }
+
+    @Override
+    public CompletableFuture<T> toCompletableFuture() {
+        return future;
+    }
+
+    @Override
+    public String toString() {
+        if (future.isDone()) {
+            if (future.isCompletedExceptionally()) {
+                return "GroovyPromise{failed}";
+            }
+            return "GroovyPromise{completed}";
+        }
+        return "GroovyPromise{pending}";
+    }
+}
diff --git a/src/main/java/groovy/transform/Async.java b/src/main/java/groovy/transform/Async.java
new file mode 100644
index 0000000000..f8b0d509b4
--- /dev/null
+++ b/src/main/java/groovy/transform/Async.java
@@ -0,0 +1,121 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package groovy.transform;
+
+import org.codehaus.groovy.transform.GroovyASTTransformationClass;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Method annotation to make a method execute asynchronously and return a
+ * {@link java.util.concurrent.CompletableFuture CompletableFuture}.
+ * <p>
+ * When applied to a method, the {@code @Async} transformation will:
+ * <ul>
+ *   <li>Change the method's return type to {@code CompletableFuture<T>} (where {@code T} is the original return type)</li>
+ *   <li>Wrap the method body to execute asynchronously via {@code CompletableFuture.supplyAsync()}</li>
+ *   <li>Transform any {@code await(future)} calls within the method to use {@link AsyncUtils#await}</li>
+ * </ul>
+ * <p>
+ * The {@code await(future)} pattern inside an {@code @Async} method blocks the async thread until the
+ * given future completes and returns the unwrapped result. This provides a sequential programming model
+ * over asynchronous operations, similar to JavaScript's {@code async/await} or C#'s {@code async/await}.
+ * <p>
+ * <em>Example usage:</em>
+ * <pre>
+ * import groovy.transform.Async
+ * import java.util.concurrent.CompletableFuture
+ *
+ * class DataService {
+ *
+ *     {@code @}Async
+ *     def fetchUser(long id) {
+ *         def profile = await(fetchProfile(id))
+ *         def orders = await(fetchOrders(id))
+ *         return [profile: profile, orders: orders]
+ *     }
+ *
+ *     CompletableFuture&lt;Map&gt; fetchProfile(long id) {
+ *         CompletableFuture.supplyAsync { [name: "User$id"] }
+ *     }
+ *
+ *     CompletableFuture&lt;List&gt; fetchOrders(long id) {
+ *         CompletableFuture.supplyAsync { ["order1", "order2"] }
+ *     }
+ * }
+ *
+ * def service = new DataService()
+ * def future = service.fetchUser(1)       // returns CompletableFuture
+ * def result = future.get()               // blocks until complete
+ * assert result.profile.name == 'User1'
+ * assert result.orders.size() == 2
+ * </pre>
+ * <p>
+ * For ad-hoc async execution without annotating a method, use {@link AsyncUtils#async}:
+ * <pre>
+ * import static groovy.transform.AsyncUtils.*
+ *
+ * def future = async {
+ *     def data = await(fetchFromRemote())
+ *     return process(data)
+ * }
+ * def result = future.get()
+ * </pre>
+ * <p>
+ * <b>Custom Executor:</b> By default, tasks run on the {@code ForkJoinPool.commonPool()}.
+ * You can specify a custom executor by providing the name of a field in the class:
+ * <pre>
+ * class MyService {
+ *     private final Executor myExecutor = Executors.newFixedThreadPool(10)
+ *
+ *     {@code @}Async(executor = "myExecutor")
+ *     def fetchData() {
+ *         return await(remoteCall())
+ *     }
+ * }
+ * </pre>
+ * <p>
+ * <b>Restrictions:</b>
+ * <ul>
+ *   <li>Cannot be applied to abstract methods</li>
+ *   <li>Cannot be applied to constructors</li>
+ *   <li>Cannot be applied to methods that already return {@code CompletableFuture}</li>
+ * </ul>
+ *
+ * @see AsyncUtils
+ * @since 6.0.0
+ */
+@Documented
+@Retention(RetentionPolicy.SOURCE)
+@Target(ElementType.METHOD)
+@GroovyASTTransformationClass("org.codehaus.groovy.transform.AsyncASTTransformation")
+public @interface Async {
+    /**
+     * Optional name of a field in the declaring class that holds an
+     * {@link java.util.concurrent.Executor Executor} to use for async execution.
+     * If empty (the default), uses {@link java.util.concurrent.ForkJoinPool#commonPool()}.
+     *
+     * @return the executor field name, or empty string for the default executor
+     */
+    String executor() default "";
+}
diff --git a/src/main/java/groovy/transform/AsyncUtils.java b/src/main/java/groovy/transform/AsyncUtils.java
new file mode 100644
index 0000000000..45845f9b09
--- /dev/null
+++ b/src/main/java/groovy/transform/AsyncUtils.java
@@ -0,0 +1,524 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package groovy.transform;
+
+import groovy.concurrent.AsyncStream;
+import groovy.concurrent.AsyncStreamGenerator;
+import groovy.concurrent.Awaitable;
+import groovy.concurrent.AwaitableAdapterRegistry;
+import groovy.concurrent.AwaitResult;
+import groovy.concurrent.GroovyPromise;
+import groovy.lang.Closure;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.Future;
+
+/**
+ * Runtime support for the {@code async}/{@code await} language feature and the
+ * {@link Async} annotation.
+ * <p>
+ * All overloads of {@code await()} go through the {@link AwaitableAdapterRegistry}
+ * so that third-party async types (RxJava {@code Single}, Reactor {@code Mono},
+ * etc.) are supported transparently once an adapter is registered.
+ * <p>
+ * Inside {@code async} methods, the {@code await expr} expression and
+ * {@code await(future)} calls are compiled to {@code AsyncUtils.await(expr)}.
+ * <p>
+ * Standalone usage:
+ * <pre>
+ * import static groovy.transform.AsyncUtils.*
+ *
+ * def future = async {
+ *     def result = await someAsyncCall()
+ *     return result * 2
+ * }
+ * </pre>
+ *
+ * @see Async
+ * @see Awaitable
+ * @see AwaitableAdapterRegistry
+ * @since 6.0.0
+ */
+public class AsyncUtils {
+
+    /**
+     * {@code true} if the running JVM supports virtual threads (JDK 21+).
+     * Detected once at class-load time via {@link MethodHandle} reflection.
+     */
+    private static final boolean VIRTUAL_THREADS_AVAILABLE;
+
+    private static final Executor VIRTUAL_THREAD_EXECUTOR;
+
+    static {
+        Executor vtExecutor = null;
+        boolean vtAvailable = false;
+        try {
+            MethodHandle mh = MethodHandles.lookup().findStatic(
+                    Executors.class, "newVirtualThreadPerTaskExecutor",
+                    MethodType.methodType(ExecutorService.class));
+            vtExecutor = (Executor) mh.invoke();
+            vtAvailable = true;
+        } catch (Throwable ignored) {
+            // JDK < 21 — virtual threads not available
+        }
+        VIRTUAL_THREAD_EXECUTOR = vtExecutor;
+        VIRTUAL_THREADS_AVAILABLE = vtAvailable;
+    }
+
+    private static volatile Executor defaultExecutor =
+            VIRTUAL_THREADS_AVAILABLE ? VIRTUAL_THREAD_EXECUTOR : ForkJoinPool.commonPool();
+
+    private AsyncUtils() { }
+
+    // ---- await overloads ------------------------------------------------
+
+    /**
+     * Awaits the result of an {@link Awaitable}.
+     * <p>
+     * Exception handling follows the same principle as C# and
+     * JavaScript: the <em>original</em> exception is rethrown transparently,
+     * even if it is a checked exception. Wrapper layers
+     * ({@link ExecutionException}, {@link CompletionException}) are fully
+     * stripped so that {@code try/catch} blocks around {@code await} work
+     * with the actual exception type.
+     */
+    public static <T> T await(Awaitable<T> awaitable) {
+        try {
+            return awaitable.get();
+        } catch (ExecutionException e) {
+            throw rethrowUnwrapped(e);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new CancellationException("Interrupted while awaiting");
+        }
+    }
+
+    /**
+     * Awaits the result of a {@link CompletableFuture}.
+     */
+    public static <T> T await(CompletableFuture<T> future) {
+        try {
+            return future.join();
+        } catch (CompletionException e) {
+            throw rethrowUnwrapped(e);
+        } catch (CancellationException e) {
+            throw e;
+        }
+    }
+
+    /**
+     * Awaits the result of a {@link CompletionStage}.
+     */
+    public static <T> T await(CompletionStage<T> stage) {
+        return await(stage.toCompletableFuture());
+    }
+
+    /**
+     * Awaits the result of a {@link Future}.
+     */
+    public static <T> T await(Future<T> future) {
+        if (future instanceof CompletableFuture<T> cf) {
+            return await(cf);
+        }
+        try {
+            return future.get();
+        } catch (ExecutionException e) {
+            throw rethrowUnwrapped(e);
+        } catch (CancellationException e) {
+            throw e;
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new CancellationException("Interrupted while awaiting future");
+        }
+    }
+
+    /**
+     * Awaits an arbitrary object by adapting it to {@link Awaitable} via the
+     * {@link AwaitableAdapterRegistry}. This is the fallback overload called
+     * by the {@code await} expression when the type is not statically known.
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> T await(Object source) {
+        if (source == null) return null;
+        if (source instanceof Awaitable) return await((Awaitable<T>) source);
+        if (source instanceof CompletableFuture) return await((CompletableFuture<T>) source);
+        if (source instanceof CompletionStage) return await((CompletionStage<T>) source);
+        if (source instanceof Future) return await((Future<T>) source);
+        return await(AwaitableAdapterRegistry.<T>toAwaitable(source));
+    }
+
+    // ---- async ----------------------------------------------------------
+
+    /**
+     * Executes the given closure asynchronously on the specified executor,
+     * returning an {@link Awaitable}. This is the <b>unified entry point</b>
+     * used by both the AST transformation ({@code @Async} methods) and the
+     * runtime ({@code async} closures/lambdas).
+     * <p>
+     * Exceptions thrown inside the closure are wrapped in
+     * {@link CompletionException} (via {@link #wrapForFuture}) so that the
+     * {@code await} methods can unwrap them transparently.
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> Awaitable<T> executeAsync(Closure<T> closure, Executor executor) {
+        return GroovyPromise.of(CompletableFuture.supplyAsync(() -> {
+            try {
+                return closure.call();
+            } catch (Throwable t) {
+                throw wrapForFuture(t);
+            }
+        }, executor));
+    }
+
+    /**
+     * Void variant of {@link #executeAsync(Closure, Executor)} for
+     * {@code @Async void} methods.
+     */
+    public static Awaitable<Void> executeAsyncVoid(Closure<?> closure, Executor executor) {
+        return GroovyPromise.of(CompletableFuture.runAsync(() -> {
+            try {
+                closure.call();
+            } catch (Throwable t) {
+                throw wrapForFuture(t);
+            }
+        }, executor));
+    }
+
+    /**
+     * Executes the given closure asynchronously using the default executor,
+     * returning an {@link Awaitable}.
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> Awaitable<T> async(Closure<T> closure) {
+        return executeAsync(closure, defaultExecutor);
+    }
+
+    /**
+     * Wraps a closure so that calling it executes the body asynchronously
+     * and returns an {@link Awaitable}. This is used by the {@code async}
+     * keyword on closures/lambdas that have parameters:
+     * <pre>
+     *   def asyncAdd = async { x, y -&gt; x + y }
+     *   def result = await asyncAdd(1, 2) // Awaitable&lt;Integer&gt;
+     * </pre>
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> Closure<Awaitable<T>> wrapAsync(Closure<T> closure) {
+        return new Closure<Awaitable<T>>(closure.getOwner(), closure.getThisObject()) {
+            @SuppressWarnings("unused")
+            public Awaitable<T> doCall(Object... args) {
+                return GroovyPromise.of(CompletableFuture.supplyAsync(() -> {
+                    try {
+                        return closure.call(args);
+                    } catch (Throwable t) {
+                        throw wrapForFuture(t);
+                    }
+                }, defaultExecutor));
+            }
+        };
+    }
+
+    /**
+     * Wraps a generator closure (containing {@code yield return}) so that
+     * calling it returns an {@link groovy.concurrent.AsyncStream}. This is
+     * used by the {@code async} keyword on closures/lambdas with parameters
+     * that contain yield return statements:
+     * <pre>
+     *   def range = async { n -&gt; for (i in 1..n) { yield return i } }
+     *   for await (item in range(5)) { println item }
+     * </pre>
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> Closure<AsyncStream<T>> wrapAsyncGenerator(Closure<?> closure) {
+        return new Closure<AsyncStream<T>>(closure.getOwner(), closure.getThisObject()) {
+            @SuppressWarnings("unused")
+            public AsyncStream<T> doCall(Object... args) {
+                AsyncStreamGenerator<T> gen = new AsyncStreamGenerator<>();
+                CompletableFuture.runAsync(() -> {
+                    try {
+                        Object[] allArgs = new Object[args.length + 1];
+                        allArgs[0] = gen;
+                        System.arraycopy(args, 0, allArgs, 1, args.length);
+                        closure.call(allArgs);
+                        gen.complete();
+                    } catch (Throwable t) {
+                        gen.error(t);
+                    }
+                }, defaultExecutor);
+                return gen;
+            }
+        };
+    }
+
+    // ---- awaitAll / awaitAny / awaitAllSettled ---------------------------
+
+    /**
+     * Waits for all given awaitables and returns their results as a list.
+     * Analogous to JavaScript's {@code Promise.all()}.
+     * <p>
+     * If any awaitable fails, the <em>first</em> encountered exception is
+     * rethrown transparently (unwrapped).
+     */
+    @SuppressWarnings("unchecked")
+    public static List<Object> awaitAll(Object... awaitables) {
+        CompletableFuture<?>[] futures = new CompletableFuture[awaitables.length];
+        for (int i = 0; i < awaitables.length; i++) {
+            futures[i] = toCompletableFuture(awaitables[i]);
+        }
+        try {
+            CompletableFuture.allOf(futures).join();
+        } catch (CompletionException e) {
+            throw rethrowUnwrapped(e);
+        }
+        List<Object> results = new ArrayList<>(futures.length);
+        for (CompletableFuture<?> f : futures) {
+            results.add(f.join());
+        }
+        return results;
+    }
+
+    /**
+     * Waits for any one of the given awaitables and returns its result.
+     * Analogous to JavaScript's {@code Promise.race()}.
+     */
+    @SuppressWarnings("unchecked")
+    public static Object awaitAny(Object... awaitables) {
+        CompletableFuture<?>[] futures = new CompletableFuture[awaitables.length];
+        for (int i = 0; i < awaitables.length; i++) {
+            futures[i] = toCompletableFuture(awaitables[i]);
+        }
+        try {
+            return CompletableFuture.anyOf(futures).join();
+        } catch (CompletionException e) {
+            throw rethrowUnwrapped(e);
+        }
+    }
+
+    /**
+     * Waits for all given awaitables and returns a list of {@link AwaitResult}
+     * objects, each recording either a success value or a failure exception.
+     * Analogous to JavaScript's {@code Promise.allSettled()}.
+     * <p>
+     * Unlike {@link #awaitAll}, this method never throws — all failures are
+     * captured in the returned list so the caller can handle them individually.
+     */
+    @SuppressWarnings("unchecked")
+    public static List<AwaitResult<Object>> awaitAllSettled(Object... awaitables) {
+        CompletableFuture<?>[] futures = new CompletableFuture[awaitables.length];
+        for (int i = 0; i < awaitables.length; i++) {
+            futures[i] = toCompletableFuture(awaitables[i]);
+        }
+        // Wait for all to settle (allSettled = allOf on handle'd futures)
+        CompletableFuture.allOf(
+            java.util.Arrays.stream(futures)
+                .map(f -> f.handle((v, t) -> null))
+                .toArray(CompletableFuture[]::new)
+        ).join();
+
+        List<AwaitResult<Object>> results = new ArrayList<>(futures.length);
+        for (CompletableFuture<?> f : futures) {
+            try {
+                results.add(AwaitResult.success(f.join()));
+            } catch (CompletionException e) {
+                results.add(AwaitResult.failure(deepUnwrap(e)));
+            } catch (CancellationException e) {
+                results.add(AwaitResult.failure(e));
+            }
+        }
+        return results;
+    }
+
+    private static CompletableFuture<?> toCompletableFuture(Object source) {
+        if (source instanceof CompletableFuture<?> cf) return cf;
+        if (source instanceof Awaitable<?> a) return a.toCompletableFuture();
+        if (source instanceof CompletionStage<?> cs) return cs.toCompletableFuture();
+        return AwaitableAdapterRegistry.toAwaitable(source).toCompletableFuture();
+    }
+
+    // ---- for-await support ----------------------------------------------
+
+    /**
+     * Converts an arbitrary source to an {@link AsyncStream} via the adapter
+     * registry. Used by the {@code for await} statement's generated code.
+     * <p>
+     * Returns an empty stream for {@code null} input, following Groovy's
+     * null-safe iteration convention.
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> AsyncStream<T> toAsyncStream(Object source) {
+        if (source == null) return AsyncStream.empty();
+        if (source instanceof AsyncStream) return (AsyncStream<T>) source;
+        return AwaitableAdapterRegistry.toAsyncStream(source);
+    }
+
+    // ---- yield return / async generator ----------------------------------
+
+    /**
+     * Safety overload: if {@code yield return} is used outside an async generator
+     * context (where the compiler hasn't injected {@code $asyncGen}), this 1-arg
+     * overload will be called, producing a clear error message.
+     */
+    public static void yieldReturn(Object value) {
+        throw new IllegalStateException(
+                "yield return can only be used inside an async generator method or closure");
+    }
+
+    /**
+     * Called by the generated code for {@code yield return expr} statements.
+     * Delivers the value to the consumer via the given
+     * {@link AsyncStreamGenerator}.
+     * <p>
+     * The generator is passed explicitly as a synthetic {@code $asyncGen}
+     * parameter injected into the generator closure by the compiler — no
+     * {@link ThreadLocal} is needed.
+     *
+     * @param generator the {@link AsyncStreamGenerator} instance
+     * @param value     the value to yield
+     * @throws IllegalStateException if generator is null (programming error)
+     */
+    public static void yieldReturn(Object generator, Object value) {
+        if (generator == null) {
+            throw new IllegalStateException("yield return used outside of an async generator context");
+        }
+        ((AsyncStreamGenerator<?>) generator).yield(value);
+    }
+
+    /**
+     * Creates an {@link AsyncStream} from a generator closure. The closure
+     * receives the {@link AsyncStreamGenerator} as its first argument and
+     * may use {@code yield return} statements to produce elements.
+     * <p>
+     * This method is called by the AST transformation for {@code async}
+     * methods and closures that contain {@code yield return} statements.
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> AsyncStream<T> generateAsyncStream(Closure<?> body) {
+        AsyncStreamGenerator<T> gen = new AsyncStreamGenerator<>();
+        CompletableFuture.runAsync(() -> {
+            try {
+                body.call(gen);
+                gen.complete();
+            } catch (Throwable t) {
+                gen.error(t);
+            }
+        }, defaultExecutor);
+        return gen;
+    }
+
+    // ---- executor configuration -----------------------------------------
+
+    /**
+     * Returns {@code true} if the running JVM supports virtual threads (JDK 21+).
+     */
+    public static boolean isVirtualThreadsAvailable() {
+        return VIRTUAL_THREADS_AVAILABLE;
+    }
+
+    public static Executor getExecutor() {
+        return defaultExecutor;
+    }
+
+    public static void setExecutor(Executor executor) {
+        defaultExecutor = executor != null
+                ? executor
+                : (VIRTUAL_THREADS_AVAILABLE ? VIRTUAL_THREAD_EXECUTOR : ForkJoinPool.commonPool());
+    }
+
+    // ---- internal -------------------------------------------------------
+
+    /**
+     * Deeply unwraps nested exception wrapper layers to find the original
+     * user exception. Handles:
+     * <ul>
+     *   <li>{@link CompletionException} — from {@code CompletableFuture.join()}</li>
+     *   <li>{@link ExecutionException} — from {@code Future.get()}</li>
+     *   <li>{@link UndeclaredThrowableException} — from Groovy's closure-to-SAM
+     *       proxy when the closure throws a checked exception</li>
+     *   <li>{@link InvocationTargetException} — from reflection-based invocations</li>
+     * </ul>
+     * This handles arbitrary nesting depth that can occur when async operations
+     * are composed through multiple framework layers.
+     */
+    static Throwable deepUnwrap(Throwable t) {
+        Throwable cause = t;
+        while (cause.getCause() != null
+                && (cause instanceof CompletionException
+                    || cause instanceof ExecutionException
+                    || cause instanceof UndeclaredThrowableException
+                    || cause instanceof InvocationTargetException)) {
+            cause = cause.getCause();
+        }
+        return cause;
+    }
+
+    /**
+     * Unwraps and rethrows the original exception transparently using the
+     * "sneaky throw" technique. This is the key to exception transparency:
+     * checked exceptions are rethrown without wrapping, exactly as C#
+     * and JavaScript handle {@code await} failures.
+     * <p>
+     * {@code try { await expr } catch (IOException e) { ... }} works correctly
+     * because the original {@code IOException} is thrown as-is, not wrapped in
+     * {@code RuntimeException}.
+     */
+    private static RuntimeException rethrowUnwrapped(Throwable wrapper) {
+        Throwable cause = deepUnwrap(wrapper);
+        if (cause instanceof Error err) throw err;
+        throw sneakyThrow(cause);
+    }
+
+    /**
+     * Rethrows a checked exception without wrapping it, using an unchecked
+     * cast trick. The JVM does not actually enforce checked-exception
+     * declarations at the bytecode level — this is a compile-time-only
+     * constraint. This technique is well-established (used by Lombok
+     * runtime, etc.) and is safe.
+     */
+    @SuppressWarnings("unchecked")
+    private static <T extends Throwable> RuntimeException sneakyThrow(Throwable t) throws T {
+        throw (T) t;
+    }
+
+    /**
+     * Wraps a throwable for storage in a {@link CompletableFuture}. Unlike
+     * the old approach of wrapping checked exceptions in {@code RuntimeException},
+     * this uses {@link CompletionException} which is the expected wrapper type
+     * for {@code CompletableFuture} and is always unwrapped by our
+     * {@code await} methods.
+     */
+    private static CompletionException wrapForFuture(Throwable t) {
+        if (t instanceof CompletionException ce) return ce;
+        return new CompletionException(t);
+    }
+}
diff --git a/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java b/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
index ff07bc7fb1..6ff4cca025 100644
--- a/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
+++ b/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
@@ -97,6 +97,7 @@ import org.codehaus.groovy.ast.expr.PropertyExpression;
 import org.codehaus.groovy.ast.expr.RangeExpression;
 import org.codehaus.groovy.ast.expr.SpreadExpression;
 import org.codehaus.groovy.ast.expr.SpreadMapExpression;
+import org.codehaus.groovy.ast.expr.StaticMethodCallExpression;
 import org.codehaus.groovy.ast.expr.TernaryExpression;
 import org.codehaus.groovy.ast.expr.TupleExpression;
 import org.codehaus.groovy.ast.expr.UnaryMinusExpression;
@@ -465,7 +466,12 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
     }
 
     @Override
-    public ForStatement visitForStmtAlt(final ForStmtAltContext ctx) {
+    public Statement visitForStmtAlt(final ForStmtAltContext ctx) {
+        // 'for await' async iteration
+        if (ctx.AWAIT() != null) {
+            return visitForAwait(ctx);
+        }
+
         Function<Statement, ForStatement> maker = this.visitForControl(ctx.forControl());
 
         Statement loopBody = this.unpackStatement((Statement) this.visit(ctx.statement()));
@@ -473,6 +479,39 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
         return configureAST(maker.apply(loopBody), ctx);
     }
 
+    private Statement visitForAwait(final ForStmtAltContext ctx) {
+        ForControlContext forCtrl = ctx.forControl();
+        EnhancedForControlContext enhCtrl = forCtrl.enhancedForControl();
+        if (enhCtrl == null) {
+            throw createParsingFailedException("for await requires enhanced for syntax: for await (item in source)", ctx);
+        }
+
+        ClassNode varType = this.visitType(enhCtrl.type());
+        String varName = this.visitIdentifier(enhCtrl.identifier());
+        Expression source = (Expression) this.visit(enhCtrl.expression());
+        Statement loopBody = this.unpackStatement((Statement) this.visit(ctx.statement()));
+
+        ClassNode asyncUtilsType = ClassHelper.make("groovy.transform.AsyncUtils");
+
+        // def $asyncStream = AsyncUtils.toAsyncStream(source)
+        Expression toStreamCall = callX(asyncUtilsType, "toAsyncStream", new ArgumentListExpression(source));
+        ExpressionStatement streamDecl = new ExpressionStatement(declX(varX("$asyncStream"), toStreamCall));
+
+        // while (AsyncUtils.await($asyncStream.moveNext()))
+        Expression moveNextCall = callX(varX("$asyncStream"), "moveNext");
+        Expression awaitCall = callX(asyncUtilsType, "await", new ArgumentListExpression(moveNextCall));
+        BooleanExpression condition = new BooleanExpression(awaitCall);
+
+        // def <varName> = $asyncStream.getCurrent()
+        Expression getCurrentCall = callX(varX("$asyncStream"), "getCurrent");
+        ExpressionStatement getItemStmt = new ExpressionStatement(declX(varX(varName, varType), getCurrentCall));
+
+        BlockStatement whileBody = block(getItemStmt, loopBody);
+        WhileStatement whileStmt = new WhileStatement(condition, whileBody);
+
+        return configureAST(block(streamDecl, whileStmt), ctx);
+    }
+
     @Override
     public Function<Statement, ForStatement> visitForControl(final ForControlContext ctx) {
         // e.g. `for (var e : x) { }` and `for (e in x) { }`
@@ -865,6 +904,15 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
         return configureAST(this.visitYieldStatement(ctx.yieldStatement()), ctx);
     }
 
+    @Override
+    public ExpressionStatement visitYieldReturnStmtAlt(final YieldReturnStmtAltContext ctx) {
+        Expression expr = (Expression) this.visit(ctx.expression());
+        Expression yieldCall = callX(
+                ClassHelper.make("groovy.transform.AsyncUtils"), "yieldReturn",
+                new ArgumentListExpression(expr));
+        return configureAST(new ExpressionStatement(yieldCall), ctx);
+    }
+
     @Override
     public ContinueStatement visitContinueStatement(final ContinueStatementContext ctx) {
         if (switchExpressionRuleContextStack.peek() instanceof SwitchExpressionContext) {
@@ -1699,6 +1747,12 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
         } else { // script method declaration
             methodNode = createScriptMethodNode(modifierManager, methodName, returnType, parameters, exceptions, code);
         }
+
+        // Inject @Async annotation for methods declared with the 'async' keyword modifier
+        if (modifierManager.containsAny(ASYNC)) {
+            methodNode.addAnnotation(new AnnotationNode(ClassHelper.make("groovy.transform.Async")));
+        }
+
         anonymousInnerClassList.forEach(e -> e.setEnclosingMethod(methodNode));
 
         methodNode.setGenericsTypes(this.visitTypeParameters(ctx.typeParameters()));
@@ -2877,6 +2931,102 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
         throw createParsingFailedException("Unsupported unary expression: " + ctx.getText(), ctx);
     }
 
+    @Override
+    public Expression visitAwaitExprAlt(final AwaitExprAltContext ctx) {
+        Expression expr = (Expression) this.visit(ctx.expression());
+        return configureAST(
+                callX(ClassHelper.make("groovy.transform.AsyncUtils"), "await",
+                        new ArgumentListExpression(expr)),
+                ctx);
+    }
+
+    @Override
+    public Expression visitAsyncClosureExprAlt(final AsyncClosureExprAltContext ctx) {
+        ClosureExpression closure = this.visitClosureOrLambdaExpression(ctx.closureOrLambdaExpression());
+        boolean hasUserParams = closure.getParameters() != null && closure.getParameters().length > 0;
+        boolean hasYieldReturn = containsYieldReturnCalls(closure);
+
+        ClassNode asyncUtils = ClassHelper.make("groovy.transform.AsyncUtils");
+        if (hasYieldReturn) {
+            // Inject synthetic $asyncGen as first parameter — rebuild closure
+            Parameter genParam = new Parameter(ClassHelper.DYNAMIC_TYPE, "$asyncGen");
+            Parameter[] existingParams = closure.getParameters();
+            Parameter[] newParams;
+            if (hasUserParams) {
+                newParams = new Parameter[existingParams.length + 1];
+                newParams[0] = genParam;
+                System.arraycopy(existingParams, 0, newParams, 1, existingParams.length);
+            } else {
+                newParams = new Parameter[]{genParam};
+            }
+            ClosureExpression genClosure = new ClosureExpression(newParams, closure.getCode());
+            genClosure.setVariableScope(closure.getVariableScope());
+            genClosure.setSourcePosition(closure);
+            // Transform yieldReturn(expr) → yieldReturn($asyncGen, expr)
+            injectGenParamIntoYieldReturnCalls(genClosure.getCode(), genParam);
+            String method = hasUserParams ? "wrapAsyncGenerator" : "generateAsyncStream";
+            return configureAST(callX(asyncUtils, method, new ArgumentListExpression(genClosure)), ctx);
+        } else {
+            String method = hasUserParams ? "wrapAsync" : "async";
+            return configureAST(callX(asyncUtils, method, new ArgumentListExpression(closure)), ctx);
+        }
+    }
+
+    /**
+     * Walks statement tree and transforms {@code AsyncUtils.yieldReturn(expr)} calls to
+     * {@code AsyncUtils.yieldReturn($asyncGen, expr)}, injecting the generator parameter
+     * as the first argument. Does not descend into nested closures.
+     */
+    private static void injectGenParamIntoYieldReturnCalls(Statement code, Parameter genParam) {
+        code.visit(new org.codehaus.groovy.ast.CodeVisitorSupport() {
+            @Override
+            public void visitExpressionStatement(org.codehaus.groovy.ast.stmt.ExpressionStatement stmt) {
+                Expression expr = stmt.getExpression();
+                if (expr instanceof org.codehaus.groovy.ast.expr.StaticMethodCallExpression smce
+                        && "yieldReturn".equals(smce.getMethod())
+                        && "groovy.transform.AsyncUtils".equals(smce.getOwnerType().getName())) {
+                    VariableExpression genRef = varX("$asyncGen");
+                    genRef.setAccessedVariable(genParam);
+                    ArgumentListExpression newArgs = new ArgumentListExpression();
+                    newArgs.addExpression(genRef);
+                    if (smce.getArguments() instanceof ArgumentListExpression argList) {
+                        for (Expression arg : argList.getExpressions()) {
+                            newArgs.addExpression(arg);
+                        }
+                    }
+                    org.codehaus.groovy.ast.expr.StaticMethodCallExpression replacement =
+                            new org.codehaus.groovy.ast.expr.StaticMethodCallExpression(
+                                    smce.getOwnerType(), "yieldReturn", newArgs);
+                    replacement.setSourcePosition(smce);
+                    stmt.setExpression(replacement);
+                }
+                super.visitExpressionStatement(stmt);
+            }
+            @Override
+            public void visitClosureExpression(ClosureExpression expression) {
+                // Don't descend into nested closures
+            }
+        });
+    }
+
+    /**
+     * Checks whether a closure's AST body contains {@code AsyncUtils.yieldReturn()} calls,
+     * used to determine if an async closure is a generator.
+     */
+    private static boolean containsYieldReturnCalls(ClosureExpression closure) {
+        boolean[] found = {false};
+        closure.getCode().visit(new CodeVisitorSupport() {
+            @Override
+            public void visitStaticMethodCallExpression(StaticMethodCallExpression call) {
+                if ("yieldReturn".equals(call.getMethod())) {
+                    found[0] = true;
+                }
+                super.visitStaticMethodCallExpression(call);
+            }
+        });
+        return found[0];
+    }
+
     @Override
     public CastExpression visitCastExprAlt(final CastExprAltContext ctx) {
         Expression expr = (Expression) this.visit(ctx.expression());
diff --git a/src/main/java/org/codehaus/groovy/ast/ModifierNode.java b/src/main/java/org/codehaus/groovy/ast/ModifierNode.java
index e76e1b4849..a141ffae12 100644
--- a/src/main/java/org/codehaus/groovy/ast/ModifierNode.java
+++ b/src/main/java/org/codehaus/groovy/ast/ModifierNode.java
@@ -25,6 +25,7 @@ import java.util.Map;
 import java.util.Objects;
 
 import static org.apache.groovy.parser.antlr4.GroovyParser.ABSTRACT;
+import static org.apache.groovy.parser.antlr4.GroovyParser.ASYNC;
 import static org.apache.groovy.parser.antlr4.GroovyParser.DEF;
 import static org.apache.groovy.parser.antlr4.GroovyParser.DEFAULT;
 import static org.apache.groovy.parser.antlr4.GroovyParser.FINAL;
@@ -55,6 +56,7 @@ public class ModifierNode extends ASTNode {
     public static final int ANNOTATION_TYPE = -999;
     public static final Map<Integer, Integer> MODIFIER_OPCODE_MAP = Maps.of(
             ANNOTATION_TYPE, 0,
+            ASYNC, 0,
             DEF, 0,
             VAR, 0,
 
diff --git a/src/main/java/org/codehaus/groovy/transform/AsyncASTTransformation.java b/src/main/java/org/codehaus/groovy/transform/AsyncASTTransformation.java
new file mode 100644
index 0000000000..89c4bad4e1
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/transform/AsyncASTTransformation.java
@@ -0,0 +1,306 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.codehaus.groovy.transform;
+
+import groovy.concurrent.AsyncStream;
+import groovy.concurrent.Awaitable;
+import groovy.transform.Async;
+import groovy.transform.AsyncUtils;
+import org.codehaus.groovy.ast.ASTNode;
+import org.codehaus.groovy.ast.AnnotatedNode;
+import org.codehaus.groovy.ast.AnnotationNode;
+import org.codehaus.groovy.ast.ClassCodeExpressionTransformer;
+import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.FieldNode;
+import org.codehaus.groovy.ast.GenericsType;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.ast.Parameter;
+import org.codehaus.groovy.ast.VariableScope;
+import org.codehaus.groovy.ast.CodeVisitorSupport;
+import org.codehaus.groovy.ast.expr.ArgumentListExpression;
+import org.codehaus.groovy.ast.expr.ClassExpression;
+import org.codehaus.groovy.ast.expr.ClosureExpression;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.expr.MethodCallExpression;
+import org.codehaus.groovy.ast.expr.StaticMethodCallExpression;
+import org.codehaus.groovy.ast.expr.VariableExpression;
+import org.codehaus.groovy.ast.stmt.ExpressionStatement;
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.control.CompilePhase;
+import org.codehaus.groovy.control.SourceUnit;
+
+import static org.codehaus.groovy.ast.tools.GeneralUtils.args;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.block;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.callX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
+import static org.codehaus.groovy.ast.tools.GenericsUtils.makeClassSafeWithGenerics;
+
+/**
+ * Handles code generation for the {@link Async} annotation and the {@code async}
+ * keyword modifier.
+ * <p>
+ * Transforms the annotated method so that:
+ * <ol>
+ *   <li>{@code await(expr)} calls are redirected to {@link AsyncUtils#await}</li>
+ *   <li>The method body is wrapped in {@code GroovyPromise.of(CompletableFuture.supplyAsync(...))}
+ *       (or {@code runAsync} for void methods)</li>
+ *   <li>The return type becomes {@link Awaitable}{@code <T>}</li>
+ * </ol>
+ *
+ * @since 6.0.0
+ */
+@GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION)
+public class AsyncASTTransformation extends AbstractASTTransformation {
+
+    private static final Class<?> MY_CLASS = Async.class;
+    private static final ClassNode MY_TYPE = ClassHelper.make(MY_CLASS);
+    private static final String MY_TYPE_NAME = "@" + MY_TYPE.getNameWithoutPackage();
+    private static final ClassNode AWAITABLE_TYPE = ClassHelper.make(Awaitable.class);
+    private static final ClassNode ASYNC_STREAM_TYPE = ClassHelper.make(AsyncStream.class);
+    private static final ClassNode ASYNC_UTILS_TYPE = ClassHelper.make(AsyncUtils.class);
+
+    @Override
+    public void visit(ASTNode[] nodes, SourceUnit source) {
+        init(nodes, source);
+        AnnotatedNode parent = (AnnotatedNode) nodes[1];
+        AnnotationNode anno = (AnnotationNode) nodes[0];
+        if (!MY_TYPE.equals(anno.getClassNode())) return;
+
+        if (!(parent instanceof MethodNode mNode)) return;
+
+        // Validate
+        if (mNode.isAbstract()) {
+            addError(MY_TYPE_NAME + " cannot be applied to abstract method '" + mNode.getName() + "'", mNode);
+            return;
+        }
+        if ("<init>".equals(mNode.getName()) || "<clinit>".equals(mNode.getName())) {
+            addError(MY_TYPE_NAME + " cannot be applied to constructors", mNode);
+            return;
+        }
+        ClassNode originalReturnType = mNode.getReturnType();
+        if (AWAITABLE_TYPE.getName().equals(originalReturnType.getName())
+                || ASYNC_STREAM_TYPE.getName().equals(originalReturnType.getName())
+                || "java.util.concurrent.CompletableFuture".equals(originalReturnType.getName())) {
+            addError(MY_TYPE_NAME + " cannot be applied to a method that already returns an async type", mNode);
+            return;
+        }
+        Statement originalBody = mNode.getCode();
+        if (originalBody == null) return;
+
+        ClassNode cNode = mNode.getDeclaringClass();
+
+        // Resolve executor expression
+        String executorFieldName = getMemberStringValue(anno, "executor");
+        Expression executorExpr;
+        if (executorFieldName != null && !executorFieldName.isEmpty()) {
+            FieldNode field = cNode.getDeclaredField(executorFieldName);
+            if (field == null) {
+                addError(MY_TYPE_NAME + ": executor field '" + executorFieldName
+                        + "' not found in class " + cNode.getName(), mNode);
+                return;
+            }
+            if (mNode.isStatic() && !field.isStatic()) {
+                addError(MY_TYPE_NAME + ": executor field '" + executorFieldName
+                        + "' must be static for static method '" + mNode.getName() + "'", mNode);
+                return;
+            }
+            executorExpr = varX(executorFieldName);
+        } else {
+            executorExpr = callX(ASYNC_UTILS_TYPE, "getExecutor", new org.codehaus.groovy.ast.expr.ArgumentListExpression());
+        }
+
+        // Step 1: Transform await() method calls to AsyncUtils.await()
+        new AwaitCallTransformer(source).visitMethod(mNode);
+
+        // Step 2: Check if body contains yield return (AsyncUtils.yieldReturn) calls
+        Statement transformedBody = mNode.getCode();
+        boolean hasYieldReturn = containsYieldReturn(transformedBody, source);
+
+        Parameter[] closureParams;
+        if (hasYieldReturn) {
+            // Generator: inject $asyncGen as closure parameter so yield return
+            // can reference it directly — no ThreadLocal needed
+            closureParams = new Parameter[]{ new Parameter(ClassHelper.DYNAMIC_TYPE, "$asyncGen") };
+        } else {
+            closureParams = Parameter.EMPTY_ARRAY;
+        }
+        ClosureExpression closure = new ClosureExpression(closureParams, transformedBody);
+        VariableScope closureScope = new VariableScope(mNode.getVariableScope());
+        for (Parameter p : mNode.getParameters()) {
+            p.setClosureSharedVariable(true);
+            closureScope.putReferencedLocalVariable(p);
+        }
+        closure.setVariableScope(closureScope);
+
+        if (hasYieldReturn) {
+            // Transform yieldReturn(expr) → yieldReturn($asyncGen, expr) in closure body
+            injectGenParamIntoYieldReturnCalls(transformedBody, closureParams[0]);
+
+            // Async generator: wrap body in AsyncUtils.generateAsyncStream { ... }
+            Expression genCall = callX(ASYNC_UTILS_TYPE, "generateAsyncStream", args(closure));
+            mNode.setCode(block(returnS(genCall)));
+
+            // Return type: AsyncStream<T>
+            ClassNode innerType;
+            if (ClassHelper.isPrimitiveVoid(originalReturnType) || ClassHelper.OBJECT_TYPE.equals(originalReturnType)) {
+                innerType = ClassHelper.OBJECT_TYPE;
+            } else if (ClassHelper.isPrimitiveType(originalReturnType)) {
+                innerType = ClassHelper.getWrapper(originalReturnType);
+            } else {
+                innerType = originalReturnType;
+            }
+            ClassNode streamReturnType = makeClassSafeWithGenerics(ASYNC_STREAM_TYPE, new GenericsType(innerType));
+            mNode.setReturnType(streamReturnType);
+        } else {
+            // Regular async: delegate to AsyncUtils.executeAsync/executeAsyncVoid
+            // This ensures identical exception handling with async closures/lambdas
+            boolean isVoid = ClassHelper.isPrimitiveVoid(originalReturnType);
+            Expression asyncCall;
+            if (isVoid) {
+                asyncCall = callX(ASYNC_UTILS_TYPE, "executeAsyncVoid", args(closure, executorExpr));
+            } else {
+                asyncCall = callX(ASYNC_UTILS_TYPE, "executeAsync", args(closure, executorExpr));
+            }
+            mNode.setCode(block(returnS(asyncCall)));
+
+            // Return type: Awaitable<T>
+            ClassNode innerType;
+            if (isVoid) {
+                innerType = ClassHelper.void_WRAPPER_TYPE;
+            } else if (ClassHelper.isPrimitiveType(originalReturnType)) {
+                innerType = ClassHelper.getWrapper(originalReturnType);
+            } else {
+                innerType = originalReturnType;
+            }
+            ClassNode awaitableReturnType = makeClassSafeWithGenerics(AWAITABLE_TYPE, new GenericsType(innerType));
+            mNode.setReturnType(awaitableReturnType);
+        }
+    }
+
+    /**
+     * Walks statement tree and transforms {@code AsyncUtils.yieldReturn(expr)} calls to
+     * {@code AsyncUtils.yieldReturn($asyncGen, expr)}, injecting the generator parameter
+     * as the first argument. Does not descend into nested closures.
+     */
+    private static void injectGenParamIntoYieldReturnCalls(Statement code, Parameter genParam) {
+        code.visit(new CodeVisitorSupport() {
+            @Override
+            public void visitExpressionStatement(ExpressionStatement stmt) {
+                Expression expr = stmt.getExpression();
+                if (expr instanceof StaticMethodCallExpression smce
+                        && "yieldReturn".equals(smce.getMethod())
+                        && "groovy.transform.AsyncUtils".equals(smce.getOwnerType().getName())) {
+                    VariableExpression genRef = varX("$asyncGen");
+                    genRef.setAccessedVariable(genParam);
+                    ArgumentListExpression newArgs = new ArgumentListExpression();
+                    newArgs.addExpression(genRef);
+                    if (smce.getArguments() instanceof ArgumentListExpression argList) {
+                        for (Expression arg : argList.getExpressions()) {
+                            newArgs.addExpression(arg);
+                        }
+                    }
+                    StaticMethodCallExpression replacement =
+                            new StaticMethodCallExpression(smce.getOwnerType(), "yieldReturn", newArgs);
+                    replacement.setSourcePosition(smce);
+                    stmt.setExpression(replacement);
+                }
+                super.visitExpressionStatement(stmt);
+            }
+            @Override
+            public void visitClosureExpression(ClosureExpression expression) {
+                // Don't descend into nested closures
+            }
+        });
+    }
+
+    /**
+     * Checks whether the given statement tree contains any
+     * {@code AsyncUtils.yieldReturn()} calls, indicating this is
+     * an async generator method (returns {@link AsyncStream}).
+     */
+    private static boolean containsYieldReturn(Statement body, SourceUnit source) {
+        boolean[] found = {false};
+        new ClassCodeVisitorSupport() {
+            @Override
+            protected SourceUnit getSourceUnit() { return source; }
+
+            @Override
+            public void visitExpressionStatement(ExpressionStatement stmt) {
+                Expression expr = stmt.getExpression();
+                if (expr instanceof StaticMethodCallExpression smce
+                        && "yieldReturn".equals(smce.getMethod())) {
+                    found[0] = true;
+                }
+                super.visitExpressionStatement(stmt);
+            }
+
+            @Override
+            public void visitClosureExpression(ClosureExpression expr) {
+                // Do NOT visit into nested closures — yield return inside
+                // an async { ... } sub-closure is handled by its own wrapping
+            }
+        }.visitMethod(new MethodNode("$check", 0, ClassHelper.VOID_TYPE, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, body));
+        return found[0];
+    }
+
+    /**
+     * Walks the method body and transforms {@code await(expr)} method calls
+     * into {@code AsyncUtils.await(expr)} static calls.  With the new grammar
+     * ({@code await expr}), most calls are already compiled to
+     * {@code AsyncUtils.await()} by the parser; this transformer handles the
+     * legacy {@code await(expr)} call-style for backward compatibility.
+     */
+    private static class AwaitCallTransformer extends ClassCodeExpressionTransformer {
+
+        private final SourceUnit sourceUnit;
+
+        AwaitCallTransformer(SourceUnit sourceUnit) {
+            this.sourceUnit = sourceUnit;
+        }
+
+        @Override
+        protected SourceUnit getSourceUnit() {
+            return sourceUnit;
+        }
+
+        @Override
+        public Expression transform(Expression expr) {
+            if (expr == null) return null;
+
+            if (expr instanceof ClosureExpression ce) {
+                ce.getCode().visit(this);
+                return ce;
+            }
+
+            if (expr instanceof MethodCallExpression mce
+                    && "await".equals(mce.getMethodAsString())
+                    && mce.isImplicitThis()) {
+                Expression transformedArgs = mce.getArguments().transformExpression(this);
+                StaticMethodCallExpression replacement = callX(ASYNC_UTILS_TYPE, "await", transformedArgs);
+                replacement.setSourcePosition(expr);
+                return replacement;
+            }
+
+            return expr.transformExpression(this);
+        }
+    }
+}
diff --git a/src/test/groovy/org/codehaus/groovy/transform/AsyncAwaitSyntaxTest.groovy b/src/test/groovy/org/codehaus/groovy/transform/AsyncAwaitSyntaxTest.groovy
new file mode 100644
index 0000000000..80f7e2f9e9
--- /dev/null
+++ b/src/test/groovy/org/codehaus/groovy/transform/AsyncAwaitSyntaxTest.groovy
@@ -0,0 +1,2108 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.codehaus.groovy.transform
+
+import org.junit.jupiter.api.Test
+
+import static groovy.test.GroovyAssert.assertScript
+import static groovy.test.GroovyAssert.shouldFail
+
+/**
+ * Comprehensive syntax-level tests for the {@code async}/{@code await}/{@code for await}
+ * language features. Every test uses {@code assertScript} to exercise the actual
+ * Groovy parser and AST transformation pipeline, ensuring the grammar, AstBuilder,
+ * and runtime all work end-to-end from the developer's perspective.
+ *
+ * <p>Organized by feature area:
+ * <ol>
+ *   <li>{@code async} modifier – method declarations</li>
+ *   <li>{@code await} expression – in various statement positions</li>
+ *   <li>{@code for await} – async iteration</li>
+ *   <li>Combined patterns – real-world usage scenarios</li>
+ *   <li>Backward compatibility – {@code async}/{@code await} as identifiers</li>
+ *   <li>Error cases – syntax and semantic rejections</li>
+ * </ol>
+ */
+class AsyncAwaitSyntaxTest {
+
+    // =====================================================================
+    // 1. 'async' modifier — method declarations
+    // =====================================================================
+
+    @Test
+    void testAsyncDefMethodReturnsAwaitable() {
+        assertScript '''
+            import groovy.concurrent.Awaitable
+
+            class Svc {
+                async greet() { "hello" }
+            }
+            def r = new Svc().greet()
+            assert r instanceof Awaitable // its default implementation is `groovy.concurrent.GroovyPromise`
+            assert r.get() == "hello"
+        '''
+    }
+
+    @Test
+    void testAsyncTypedReturnMethod() {
+        assertScript '''
+            import groovy.concurrent.Awaitable
+
+            class Svc {
+                async String upper(String s) { s.toUpperCase() }
+            }
+            assert new Svc().upper("abc").get() == "ABC"
+        '''
+    }
+
+    @Test
+    void testAsyncIntReturnMethod() {
+        assertScript '''
+            import groovy.concurrent.Awaitable
+
+            class Svc {
+                async int square(int n) { n * n }
+            }
+            assert new Svc().square(7).get() == 49
+        '''
+    }
+
+    @Test
+    void testAsyncVoidMethodReturnsAwaitable() {
+        assertScript '''
+            import groovy.concurrent.Awaitable
+            import java.util.concurrent.atomic.AtomicReference
+
+            class Svc {
+                AtomicReference<String> ref = new AtomicReference<>()
+                async void store(String v) { ref.set(v) }
+            }
+            def svc = new Svc()
+            def a = svc.store("done")
+            assert a instanceof Awaitable
+            a.get()
+            assert svc.ref.get() == "done"
+        '''
+    }
+
+    @Test
+    void testAsyncStaticMethod() {
+        assertScript '''
+            import groovy.concurrent.Awaitable
+
+            class Util {
+                async static def add(int a, int b) { a + b }
+            }
+            assert Util.add(3, 4).get() == 7
+        '''
+    }
+
+    @Test
+    void testAsyncDefInScript() {
+        assertScript '''
+            import groovy.concurrent.Awaitable
+
+            async multiply(int a, int b) { a * b }
+
+            def r = multiply(6, 7)
+            assert r instanceof Awaitable
+            assert r.get() == 42
+        '''
+    }
+
+    @Test
+    void testAsyncMethodWithMultipleParams() {
+        assertScript '''
+            class Svc {
+                async concat(String a, String b, String sep) {
+                    return a + sep + b
+                }
+            }
+            assert new Svc().concat("foo", "bar", "-").get() == "foo-bar"
+        '''
+    }
+
+    @Test
+    void testAsyncMethodWithDefaultParams() {
+        assertScript '''
+            class Svc {
+                async greet(String name, String greeting = "Hello") {
+                    "${greeting}, ${name}!"
+                }
+            }
+            def svc = new Svc()
+            assert svc.greet("World").get() == "Hello, World!"
+            assert svc.greet("World", "Hi").get() == "Hi, World!"
+        '''
+    }
+
+    @Test
+    void testAsyncMethodWithVarargs() {
+        assertScript '''
+            class Svc {
+                async sum(int... nums) {
+                    nums.sum()
+                }
+            }
+            assert new Svc().sum(1, 2, 3, 4).get() == 10
+        '''
+    }
+
+    @Test
+    void testAsyncMethodWithClosureParam() {
+        assertScript '''
+            class Svc {
+                async transform(String s, Closure fn) {
+                    fn(s)
+                }
+            }
+            assert new Svc().transform("hello") { it.reverse() }.get() == "olleh"
+        '''
+    }
+
+    @Test
+    void testAsyncMethodRunsAsynchronously() {
+        assertScript '''
+            class Svc {
+                async threadName() { Thread.currentThread().name }
+            }
+            def name = new Svc().threadName().get()
+            assert name != Thread.currentThread().name
+        '''
+    }
+
+    // =====================================================================
+    // 2. 'await' expression — in various statement positions
+    // =====================================================================
+
+    @Test
+    void testAwaitCompletableFuture() {
+        assertScript '''
+            import java.util.concurrent.CompletableFuture
+
+            class Svc {
+                async fetch() {
+                    def v = await CompletableFuture.supplyAsync { 42 }
+                    return v
+                }
+            }
+            assert new Svc().fetch().get() == 42
+        '''
+    }
+
+    @Test
+    void testAwaitAwaitableObject() {
+        assertScript '''
+            import groovy.concurrent.Awaitable
+
+            class Svc {
+                async inner() { "from-inner" }
+                async outer() {
+                    def v = await inner()
+                    return "got: ${v}"
+                }
+            }
+            assert new Svc().outer().get() == "got: from-inner"
+        '''
+    }
+
+    @Test
+    void testAwaitWithParenthesizedExpression() {
+        assertScript '''
+            import java.util.concurrent.CompletableFuture
+
+            class Svc {
+                async compute() {
+                    def v = await(CompletableFuture.supplyAsync { 99 })
+                    return v
+                }
+            }
+            assert new Svc().compute().get() == 99
+        '''
+    }
+
+    @Test
+    void testAwaitInAssignment() {
+        assertScript '''
+            import java.util.concurrent.CompletableFuture
+
+            class Svc {
+                async work() {
+                    String result
+                    result = await CompletableFuture.supplyAsync { "assigned" }
+                    return result
+                }
+            }
+            assert new Svc().work().get() == "assigned"
+        '''
+    }
+
+    @Test
+    void testAwaitInArithmeticExpression() {
+        assertScript '''
+            import java.util.concurrent.CompletableFuture
+
+            class Svc {
+                async calc() {
+                    def a = await CompletableFuture.supplyAsync { 10 }
+                    def b = await CompletableFuture.supplyAsync { 20 }
+                    return a + b + 5
+                }
+            }
+            assert new Svc().calc().get() == 35
+        '''
+    }
+
+    @Test
+    void testMultipleSequentialAwaits() {
+        assertScript '''
+            import java.util.concurrent.CompletableFuture
+
+            class Svc {
+                async pipeline() {
+                    def a = await CompletableFuture.supplyAsync { "step1" }
+                    def b = await CompletableFuture.supplyAsync { "${a}->step2" }
+                    def c = await CompletableFuture.supplyAsync { "${b}->step3" }
+                    return c
+                }
+            }
+            assert new Svc().pipeline().get() == "step1->step2->step3"
+        '''
+    }
+
+    @Test
+    void testAwaitInReturnStatement() {
+        assertScript '''
+            import java.util.concurrent.CompletableFuture
+
+            class Svc {
+                async direct() {
+                    return await CompletableFuture.supplyAsync { "direct-return" }
+                }
+            }
+            assert new Svc().direct().get() == "direct-return"
+        '''
+    }
+
+    @Test
+    void testAwaitInIfCondition() {
+        assertScript '''
+            import java.util.concurrent.CompletableFuture
+
+            class Svc {
+                async decide(boolean flag) {
+                    if (flag) {
+                        return await CompletableFuture.supplyAsync { "yes" }
+                    } else {
+                        return await CompletableFuture.supplyAsync { "no" }
+                    }
+                }
+            }
+            def svc = new Svc()
+            assert svc.decide(true).get() == "yes"
+            assert svc.decide(false).get() == "no"
+        '''
+    }
+
+    @Test
+    void testAwaitInTernaryExpression() {
+        assertScript '''
+            import java.util.concurrent.CompletableFuture
+
+            class Svc {
+                async ternary(boolean flag) {
+                    def v = flag ? (await CompletableFuture.supplyAsync { "T" })
+                                 : (await CompletableFuture.supplyAsync { "F" })
+                    return v
+                }
+            }
+            def svc = new Svc()
+            assert svc.ternary(true).get() == "T"
+            assert svc.ternary(false).get() == "F"
+        '''
+    }
+
+    @Test
+    void testAwaitInWhileLoop() {
+        assertScript '''
+            import java.util.concurrent.CompletableFuture
+
+            class Svc {
+                async countdown() {
+                    def results = []
+                    int n = 3
+                    while (n > 0) {
+                        def v = await CompletableFuture.supplyAsync { n }
+                        results << v
+                        n--
+                    }
+                    return results
+                }
+            }
+            assert new Svc().countdown().get() == [3, 2, 1]
+        '''
+    }
+
+    @Test
+    void testAwaitInForLoop() {
+        assertScript '''
+            import java.util.concurrent.CompletableFuture
+
+            class Svc {
+                async processItems() {
+                    def results = []
+                    for (int i = 0; i < 4; i++) {
+                        def v = await CompletableFuture.supplyAsync { i * 10 }
+                        results << v
+                    }
+                    return results
+                }
+            }
+            assert new Svc().processItems().get() == [0, 10, 20, 30]
+        '''
+    }
+
+    @Test
+    void testAwaitInForEachLoop() {
+        assertScript '''
+            import java.util.concurrent.CompletableFuture
+
+            class Svc {
+                async doubleAll() {
+                    def results = []
+                    for (x in [1, 2, 3]) {
+                        def v = await CompletableFuture.supplyAsync { x * 2 }
+                        results << v
+                    }
+                    return results
+                }
+            }
+            assert new Svc().doubleAll().get() == [2, 4, 6]
+        '''
+    }
+
+    @Test
+    void testAwaitInTryCatch() {
+        assertScript '''
+            import java.util.concurrent.CompletableFuture
+
+            class Svc {
+                async safe() {
+                    try {
+                        return await CompletableFuture.supplyAsync {
+                            throw new RuntimeException("boom")
+                        }
+                    } catch (RuntimeException e) {
+                        return "caught: ${e.message}"
+                    }
+                }
+            }
+            assert new Svc().safe().get() == "caught: boom"
+        '''
+    }
+
+    @Test
+    void testAwaitInTryFinally() {
+        assertScript '''
+            import java.util.concurrent.CompletableFuture
+            import java.util.concurrent.atomic.AtomicBoolean
+
+            class Svc {
+                AtomicBoolean cleaned = new AtomicBoolean(false)
+                async withCleanup() {
+                    try {
+                        return await CompletableFuture.supplyAsync { "result" }
+                    } finally {
+                        cleaned.set(true)
+                    }
+                }
+            }
+            def svc = new Svc()
+            assert svc.withCleanup().get() == "result"
+            assert svc.cleaned.get()
+        '''
+    }
+
+    @Test
+    void testAwaitInSwitchExpression() {
+        assertScript '''
+            import java.util.concurrent.CompletableFuture
+
+            class Svc {
+                async route(int code) {
+                    switch (code) {
+                        case 1: return await CompletableFuture.supplyAsync { "one" }
+                        case 2: return await CompletableFuture.supplyAsync { "two" }
+                        default: return await CompletableFuture.supplyAsync { "other" }
+                    }
+                }
+            }
+            def svc = new Svc()
+            assert svc.route(1).get() == "one"
+            assert svc.route(2).get() == "two"
+            assert svc.route(9).get() == "other"
+        '''
+    }
+
+    @Test
+    void testAwaitInStringInterpolation() {
+        assertScript '''
+            import java.util.concurrent.CompletableFuture
+
+            class Svc {
+                async greet() {
+                    def name = await CompletableFuture.supplyAsync { "World" }
+                    return "Hello, ${name}!"
+                }
+            }
+            assert new Svc().greet().get() == "Hello, World!"
+        '''
+    }
+
+    @Test
+    void testAwaitInCollectionLiteral() {
+        assertScript '''
+            import java.util.concurrent.CompletableFuture
+
+            class Svc {
+                async buildList() {
+                    def a = await CompletableFuture.supplyAsync { "x" }
+                    def b = await CompletableFuture.supplyAsync { "y" }
+                    return [a, b, "z"]
+                }
+            }
+            assert new Svc().buildList().get() == ["x", "y", "z"]
+        '''
+    }
+
+    @Test
+    void testAwaitInMapLiteral() {
+        assertScript '''
+            import java.util.concurrent.CompletableFuture
+
+            class Svc {
+                async buildMap() {
+                    def name = await CompletableFuture.supplyAsync { "Alice" }
+                    def age = await CompletableFuture.supplyAsync { 30 }
+                    return [name: name, age: age]
+                }
+            }
+            assert new Svc().buildMap().get() == [name: "Alice", age: 30]
+        '''
+    }
+
+    @Test
+    void testAwaitAsMethodArgument() {
+        assertScript '''
+            import java.util.concurrent.CompletableFuture
+
+            class Svc {
+                String format(String s) { "[${s}]" }
+                async process() {
+                    def v = await CompletableFuture.supplyAsync { "data" }
+                    return format(v)
+                }
+            }
+            assert new Svc().process().get() == "[data]"
+        '''
+    }
+
+    @Test
+    void testNestedAwaitExpressions() {
+        assertScript '''
+            import java.util.concurrent.CompletableFuture
+
+            class Svc {
+                async nested() {
+                    // await of (await result used to create another future)
+                    def inner = await CompletableFuture.supplyAsync { 5 }
+                    def outer = await CompletableFuture.supplyAsync { inner * 10 }
+                    return outer
+                }
+            }
+            assert new Svc().nested().get() == 50
+        '''
+    }
+
+    @Test
+    void testAwaitInClosureInsideAsyncMethod() {
+        assertScript '''
+            import java.util.concurrent.CompletableFuture
+
+            class Svc {
+                async transform() {
+                    def items = [1, 2, 3]
+                    // Note: await inside collect closure — this works because
+                    // the closure executes synchronously within the async method
+                    def results = items.collect { n ->
+                        await CompletableFuture.supplyAsync { n * 100 }
+                    }
+                    return results
+                }
+            }
+            assert new Svc().transform().get() == [100, 200, 300]
+        '''
+    }
+
+    @Test
+    void testAwaitNullResult() {
+        assertScript '''
+            import java.util.concurrent.CompletableFuture
+
+            class Svc {
+                async fetchNull() {
+                    def v = await CompletableFuture.supplyAsync { null }
+                    return v
+                }
+            }
+            assert new Svc().fetchNull().get() == null
+        '''
+    }
+
+    @Test
+    void testAwaitBooleanResult() {
+        assertScript '''
+            import java.util.concurrent.CompletableFuture
+
+            class Svc {
+                async check() {
+                    boolean ok = await CompletableFuture.supplyAsync { true }
+                    return ok
+                }
+            }
+            assert new Svc().check().get() == true
+        '''
+    }
+
+    @Test
+    void testAwaitChainedMethodCallOnResult() {
+        assertScript '''
+            import java.util.concurrent.CompletableFuture
+
+            class Svc {
+                async process() {
+                    def s = await CompletableFuture.supplyAsync { "hello world" }
+                    return s.toUpperCase().split(" ").toList()
+                }
+            }
+            assert new Svc().process().get() == ["HELLO", "WORLD"]
+        '''
+    }
+
+    // =====================================================================
+    // 3. 'for await' — async iteration
+    // =====================================================================
+
+    @Test
+    void testForAwaitBasicIteration() {
+        assertScript '''
+            class Svc {
+                async collect() {
+                    def results = []
+                    for await (item in [10, 20, 30]) {
+                        results << item
+                    }
+                    return results
+                }
+            }
+            assert new Svc().collect().get() == [10, 20, 30]
+        '''
+    }
+
+    @Test
+    void testForAwaitBasicIteration_2() {
+        assertScript '''
+            class Svc {
+                async collect() {
+                    def results = []
+                    for await (item : [10, 20, 30]) {
+                        results << item
+                    }
+                    return results
+                }
+            }
+            assert new Svc().collect().get() == [10, 20, 30]
+        '''
+    }
+
+    @Test
+    void testForAwaitWithTransformation() {
+        assertScript '''
+            class Svc {
+                async doubleAll() {
+                    def results = []
+                    for await (item in [1, 2, 3, 4]) {
+                        results << item * 2
+                    }
+                    return results
+                }
+            }
+            assert new Svc().doubleAll().get() == [2, 4, 6, 8]
+        '''
+    }
+
+    @Test
+    void testForAwaitWithBreak() {
+        assertScript '''
+            class Svc {
+                async findFirst() {
+                    for await (item in [5, 10, 15, 20, 25]) {
+                        if (item > 12) return item
+                    }
+                    return -1
+                }
+            }
+            assert new Svc().findFirst().get() == 15
+        '''
+    }
+
+    @Test
+    void testForAwaitWithContinue() {
+        assertScript '''
+            class Svc {
+                async skipOdds() {
+                    def results = []
+                    for await (item in [1, 2, 3, 4, 5, 6]) {
+                        if (item % 2 != 0) continue
+                        results << item
+                    }
+                    return results
+                }
+            }
+            assert new Svc().skipOdds().get() == [2, 4, 6]
+        '''
+    }
+
+    @Test
+    void testForAwaitWithTypedVariable() {
+        assertScript '''
+            class Svc {
+                async typed() {
+                    def results = []
+                    for await (String s in ["hello", "world"]) {
+                        results << s.toUpperCase()
+                    }
+                    return results
+                }
+            }
+            assert new Svc().typed().get() == ["HELLO", "WORLD"]
+        '''
+    }
+
+    @Test
+    void testForAwaitEmptySource() {
+        assertScript '''
+            class Svc {
+                async empty() {
+                    def results = []
+                    for await (item in []) {
+                        results << item
+                    }
+                    return results
+                }
+            }
+            assert new Svc().empty().get() == []
+        '''
+    }
+
+    @Test
+    void testForAwaitSingleElement() {
+        assertScript '''
+            class Svc {
+                async single() {
+                    def results = []
+                    for await (item in ["only"]) {
+                        results << item
+                    }
+                    return results
+                }
+            }
+            assert new Svc().single().get() == ["only"]
+        '''
+    }
+
+    @Test
+    void testForAwaitWithIndex() {
+        assertScript '''
+            class Svc {
+                async indexed() {
+                    def results = []
+                    int idx = 0
+                    for await (item in ["a", "b", "c"]) {
+                        results << "${idx}:${item}"
+                        idx++
+                    }
+                    return results
+                }
+            }
+            assert new Svc().indexed().get() == ["0:a", "1:b", "2:c"]
+        '''
+    }
+
+    @Test
+    void testForAwaitNestedInRegularFor() {
+        assertScript '''
+            class Svc {
+                async nested() {
+                    def results = []
+                    for (prefix in ["A", "B"]) {
+                        for await (n in [1, 2]) {
+                            results << "${prefix}${n}"
+                        }
+                    }
+                    return results
+                }
+            }
+            assert new Svc().nested().get() == ["A1", "A2", "B1", "B2"]
+        '''
+    }
+
+    @Test
+    void testRegularForNestedInForAwait() {
+        assertScript '''
+            class Svc {
+                async nested() {
+                    def results = []
+                    for await (base in [10, 20]) {
+                        for (int i = 0; i < 3; i++) {
+                            results << base + i
+                        }
+                    }
+                    return results
+                }
+            }
+            assert new Svc().nested().get() == [10, 11, 12, 20, 21, 22]
+        '''
+    }
+
+    @Test
+    void testForAwaitWithAwaitInsideBody() {
+        assertScript '''
+            import java.util.concurrent.CompletableFuture
+
+            class Svc {
+                async process() {
+                    def results = []
+                    for await (item in [1, 2, 3]) {
+                        def doubled = await CompletableFuture.supplyAsync { item * 2 }
+                        results << doubled
+                    }
+                    return results
+                }
+            }
+            assert new Svc().process().get() == [2, 4, 6]
+        '''
+    }
+
+    @Test
+    void testForAwaitWithAsyncStream() {
+        assertScript '''
+            import groovy.concurrent.AsyncStream
+            import groovy.concurrent.Awaitable
+
+            class CountStream implements AsyncStream<Integer> {
+                private int current = 0
+                private final int max
+                CountStream(int max) { this.max = max }
+
+                Awaitable<Boolean> moveNext() {
+                    current++
+                    return Awaitable.of(current <= max)
+                }
+                Integer getCurrent() { current }
+            }
+
+            class Svc {
+                async sum() {
+                    int total = 0
+                    for await (n in new CountStream(5)) {
+                        total += n
+                    }
+                    return total
+                }
+            }
+            assert new Svc().sum().get() == 15
+        '''
+    }
+
+    @Test
+    void testMultipleForAwaitSequential() {
+        assertScript '''
+            class Svc {
+                async multi() {
+                    def r1 = []
+                    for await (x in [1, 2]) { r1 << x }
+                    def r2 = []
+                    for await (y in [3, 4]) { r2 << y }
+                    return [r1, r2]
+                }
+            }
+            assert new Svc().multi().get() == [[1, 2], [3, 4]]
+        '''
+    }
+
+    @Test
+    void testForAwaitWithAnnotation() {
+        assertScript '''
+            import groovy.transform.Async
+
+            class Svc {
+                @Async
+                def collectItems() {
+                    def results = []
+                    for await (item in [100, 200, 300]) {
+                        results << item
+                    }
+                    return results
+                }
+            }
+            assert new Svc().collectItems().get() == [100, 200, 300]
+        '''
+    }
+
+    @Test
+    void testForAwaitInScript() {
+        assertScript '''
+            import groovy.transform.Async
+            import groovy.concurrent.Awaitable
+
+            @Async
+            def gatherItems() {
+                def results = []
+                for await (item in ["x", "y", "z"]) {
+                    results << item
+                }
+                return results
+            }
+
+            assert gatherItems().get() == ["x", "y", "z"]
+        '''
+    }
+
+    @Test
+    void testForAwaitWithTryCatch() {
+        assertScript '''
+            class Svc {
+                async safeIterate() {
+                    def results = []
+                    try {
+                        for await (item in [1, 2, 3]) {
+                            results << item
+                        }
+                    } catch (Exception e) {
+                        results << "error"
+                    }
+                    return results
+                }
+            }
+            assert new Svc().safeIterate().get() == [1, 2, 3]
+        '''
+    }
+
+    // =====================================================================
+    // 4. Combined patterns — real-world usage scenarios
+    // =====================================================================
+
+    @Test
+    void testAsyncServiceCallingAsyncService() {
+        assertScript '''
+            class UserService {
+                async findUser(long id) {
+                    return [id: id, name: "User${id}"]
+                }
+            }
+
+            class OrderService {
+                async findOrders(long userId) {
+                    return (1..3).collect { [orderId: it, userId: userId] }
+                }
+            }
+
+            class AggregatorService {
+                def userService = new UserService()
+                def orderService = new OrderService()
+
+                async getUserWithOrders(long id) {
+                    def user = await userService.findUser(id)
+                    def orders = await orderService.findOrders(id)
+                    return [user: user, orders: orders]
+                }
+            }
+
+            def agg = new AggregatorService()
+            def result = agg.getUserWithOrders(42).get()
+            assert result.user.name == "User42"
+            assert result.orders.size() == 3
+            assert result.orders[0].userId == 42
+        '''
+    }
+
+    @Test
+    void testAsyncWithExceptionPropagation() {
+        assertScript '''
+            import java.util.concurrent.ExecutionException
+
+            class Svc {
+                async fail() {
+                    throw new IllegalArgumentException("bad input")
+                }
+            }
+            try {
+                new Svc().fail().get()
+                assert false : "expected exception"
+            } catch (ExecutionException e) {
+                assert e.cause instanceof IllegalArgumentException
+                assert e.cause.message == "bad input"
+            }
+        '''
+    }
+
+    @Test
+    void testAwaitPropagatesExceptionToCallerAsync() {
+        assertScript '''
+            class Svc {
+                async failInner() {
+                    throw new IllegalStateException("inner-fail")
+                }
+                async caller() {
+                    try {
+                        await failInner()
+                        return "should not reach"
+                    } catch (IllegalStateException e) {
+                        return "recovered: ${e.message}"
+                    }
+                }
+            }
+            assert new Svc().caller().get() == "recovered: inner-fail"
+        '''
+    }
+
+    @Test
+    void testAsyncAwaitWithCollectionProcessing() {
+        assertScript '''
+            import java.util.concurrent.CompletableFuture
+
+            class Svc {
+                async processAll(List<Integer> items) {
+                    def results = []
+                    for (item in items) {
+                        def r = await CompletableFuture.supplyAsync { item * item }
+                        results << r
+                    }
+                    return results
+                }
+            }
+            assert new Svc().processAll([1, 2, 3, 4, 5]).get() == [1, 4, 9, 16, 25]
+        '''
+    }
+
+    @Test
+    void testAsyncAwaitProducerConsumer() {
+        assertScript '''
+            import java.util.concurrent.CompletableFuture
+
+            class Producer {
+                async produce(int n) { n * 10 }
+            }
+            class Consumer {
+                def producer = new Producer()
+                async consume() {
+                    def sum = 0
+                    for (i in 1..5) {
+                        sum += await producer.produce(i)
+                    }
+                    return sum
+                }
+            }
+            // 10 + 20 + 30 + 40 + 50 = 150
+            assert new Consumer().consume().get() == 150
+        '''
+    }
+
+    @Test
+    void testAsyncAwaitParallelThenGather() {
+        assertScript '''
+            import java.util.concurrent.CompletableFuture
+
+            class Svc {
+                async step(int n) { n * 2 }
+
+                async parallelGather() {
+                    // Launch multiple async tasks
+                    def f1 = step(1)
+                    def f2 = step(2)
+                    def f3 = step(3)
+                    // Await all results
+                    return [await f1, await f2, await f3]
+                }
+            }
+            assert new Svc().parallelGather().get() == [2, 4, 6]
+        '''
+    }
+
+    @Test
+    void testAsyncAwaitChainedTransformation() {
+        assertScript '''
+            class Pipeline {
+                async step1(String input) { input.toUpperCase() }
+                async step2(String input) { input.reverse() }
+                async step3(String input) { "[${input}]" }
+
+                async run(String input) {
+                    def r1 = await step1(input)
+                    def r2 = await step2(r1)
+                    return await step3(r2)
+                }
+            }
+            assert new Pipeline().run("abc").get() == "[CBA]"
+        '''
+    }
+
+    @Test
+    void testAsyncMixedAnnotationAndKeyword() {
+        assertScript '''
+            import groovy.transform.Async
+            import groovy.concurrent.Awaitable
+
+            class Svc {
+                @Async
+                def fromAnnotation() { "annotated" }
+
+                async fromKeyword() { "keyword" }
+
+                async combine() {
+                    def a = await fromAnnotation()
+                    def b = await fromKeyword()
+                    return "${a}+${b}"
+                }
+            }
+            assert new Svc().combine().get() == "annotated+keyword"
+        '''
+    }
+
+    @Test
+    void testAsyncWithConditionalAwait() {
+        assertScript '''
+            import java.util.concurrent.CompletableFuture
+
+            class Svc {
+                async fetchIf(boolean shouldFetch) {
+                    if (shouldFetch) {
+                        return await CompletableFuture.supplyAsync { "fetched" }
+                    }
+                    return "cached"
+                }
+            }
+            def svc = new Svc()
+            assert svc.fetchIf(true).get() == "fetched"
+            assert svc.fetchIf(false).get() == "cached"
+        '''
+    }
+
+    @Test
+    void testAsyncRecursion() {
+        assertScript '''
+            class Svc {
+                async factorial(int n) {
+                    if (n <= 1) return 1
+                    def sub = await factorial(n - 1)
+                    return n * sub
+                }
+            }
+            assert new Svc().factorial(5).get() == 120
+        '''
+    }
+
+    @Test
+    void testAsyncWithRetry() {
+        assertScript '''
+            import java.util.concurrent.CompletableFuture
+            import java.util.concurrent.atomic.AtomicInteger
+
+            class Svc {
+                AtomicInteger attempts = new AtomicInteger(0)
+
+                async unreliable() {
+                    if (attempts.incrementAndGet() < 3) {
+                        throw new RuntimeException("not yet")
+                    }
+                    return "success"
+                }
+
+                async withRetry(int maxRetries) {
+                    for (int i = 0; i < maxRetries; i++) {
+                        try {
+                            return await unreliable()
+                        } catch (RuntimeException e) {
+                            if (i == maxRetries - 1) throw e
+                        }
+                    }
+                }
+            }
+            assert new Svc().withRetry(5).get() == "success"
+        '''
+    }
+
+    @Test
+    void testAsyncWithTimeout() {
+        assertScript '''
+            import groovy.concurrent.Awaitable
+            import java.util.concurrent.TimeUnit
+            import java.util.concurrent.TimeoutException
+
+            class Svc {
+                async slow() {
+                    Thread.sleep(2000)
+                    return "done"
+                }
+            }
+            def a = new Svc().slow()
+            try {
+                a.get(50, TimeUnit.MILLISECONDS)
+                assert false : "expected timeout"
+            } catch (TimeoutException e) {
+                // expected
+            }
+        '''
+    }
+
+    @Test
+    void testAsyncWithAwaitableOf() {
+        assertScript '''
+            import groovy.concurrent.Awaitable
+
+            class Svc {
+                async useOf() {
+                    def a = await Awaitable.of(42)
+                    return a + 8
+                }
+            }
+            assert new Svc().useOf().get() == 50
+        '''
+    }
+
+    @Test
+    void testAsyncWithAwaitableThen() {
+        assertScript '''
+            import groovy.concurrent.Awaitable
+
+            class Svc {
+                async inner() { 10 }
+                async chained() {
+                    def a = inner()  // returns Awaitable<Integer>
+                    def b = a.then { it * 3 }
+                    return await b
+                }
+            }
+            assert new Svc().chained().get() == 30
+        '''
+    }
+
+    @Test
+    void testAsyncWithAwaitableThenCompose() {
+        assertScript '''
+            import groovy.concurrent.Awaitable
+
+            class Svc {
+                async step1() { 5 }
+                async step2(int v) { v * 4 }
+                async composed() {
+                    def a = step1()
+                    def b = a.thenCompose { v -> step2(v) }
+                    return await b
+                }
+            }
+            assert new Svc().composed().get() == 20
+        '''
+    }
+
+    @Test
+    void testAsyncWithAwaitableExceptionally() {
+        assertScript '''
+            import groovy.concurrent.Awaitable
+
+            class Svc {
+                async failing() {
+                    throw new RuntimeException("oops")
+                }
+                async recovered() {
+                    def a = failing().exceptionally { "fallback" }
+                    return await a
+                }
+            }
+            assert new Svc().recovered().get() == "fallback"
+        '''
+    }
+
+    @Test
+    void testConcurrentAsyncMethodsWithSyntax() {
+        assertScript '''
+            import java.util.concurrent.ConcurrentHashMap
+
+            class Svc {
+                def results = new ConcurrentHashMap()
+                async store(String key, String value) {
+                    results.put(key, value)
+                    return value
+                }
+            }
+            def svc = new Svc()
+            def futures = (1..10).collect { i -> svc.store("k${i}", "v${i}") }
+            futures.each { it.get() }
+            assert svc.results.size() == 10
+            assert svc.results["k5"] == "v5"
+        '''
+    }
+
+    @Test
+    void testScriptLevelAsyncAwait() {
+        assertScript '''
+            import java.util.concurrent.CompletableFuture
+
+            async fetchData(String key) {
+                return "data-for-${key}"
+            }
+
+            async main() {
+                def r1 = await fetchData("alpha")
+                def r2 = await fetchData("beta")
+                return [r1, r2]
+            }
+
+            def result = main().get()
+            assert result[0] == "data-for-alpha"
+            assert result[1] == "data-for-beta"
+        '''
+    }
+
+    @Test
+    void testScriptLevelForAwait() {
+        assertScript '''
+            async items() {
+                def results = []
+                for await (x in [1, 2, 3]) {
+                    results << x * 10
+                }
+                return results
+            }
+            assert items().get() == [10, 20, 30]
+        '''
+    }
+
+    @Test
+    void testAsyncWithCustomExecutor() {
+        assertScript '''
+            import java.util.concurrent.Executors
+
+            class Svc {
+                static executor = Executors.newFixedThreadPool(2)
+                async threadInfo() {
+                    return Thread.currentThread().name
+                }
+            }
+
+            groovy.transform.AsyncUtils.setExecutor(Svc.executor)
+            try {
+                def name = new Svc().threadInfo().get()
+                assert name.contains("pool")
+            } finally {
+                groovy.transform.AsyncUtils.setExecutor(null)
+                Svc.executor.shutdown()
+            }
+        '''
+    }
+
+    @Test
+    void testForAwaitCombinedWithAwaitInSameMethod() {
+        assertScript '''
+            import java.util.concurrent.CompletableFuture
+
+            class Svc {
+                async complexFlow() {
+                    def prefix = await CompletableFuture.supplyAsync { "item" }
+                    def results = []
+                    for await (n in [1, 2, 3]) {
+                        results << "${prefix}-${n}"
+                    }
+                    def suffix = await CompletableFuture.supplyAsync { "-done" }
+                    return results.join(",") + suffix
+                }
+            }
+            assert new Svc().complexFlow().get() == "item-1,item-2,item-3-done"
+        '''
+    }
+
+    @Test
+    void testAsyncMethodWithGStringReturn() {
+        assertScript '''
+            class Svc {
+                async template(String name, int age) {
+                    return "Name: ${name}, Age: ${age}"
+                }
+            }
+            assert new Svc().template("Alice", 25).get() == "Name: Alice, Age: 25"
+        '''
+    }
+
+    @Test
+    void testAsyncMethodReturningCollection() {
+        assertScript '''
+            class Svc {
+                async getList() { [1, 2, 3] }
+                async getMap() { [a: 1, b: 2] }
+                async getSet() { [1, 2, 3] as Set }
+
+                async all() {
+                    def list = await getList()
+                    def map = await getMap()
+                    def set = await getSet()
+                    return [list: list, map: map, setSize: set.size()]
+                }
+            }
+            def r = new Svc().all().get()
+            assert r.list == [1, 2, 3]
+            assert r.map == [a: 1, b: 2]
+            assert r.setSize == 3
+        '''
+    }
+
+    @Test
+    void testAsyncWithPropertyAccess() {
+        assertScript '''
+            class User {
+                String name
+                int age
+            }
+
+            class Svc {
+                async getUser() {
+                    new User(name: "Bob", age: 40)
+                }
+                async getUserName() {
+                    def user = await getUser()
+                    return user.name
+                }
+            }
+            assert new Svc().getUserName().get() == "Bob"
+        '''
+    }
+
+    @Test
+    void testAsyncWithSpreadOperator() {
+        assertScript '''
+            class Svc {
+                async getItems() { [[v: 1], [v: 2], [v: 3]] }
+                async getValues() {
+                    def items = await getItems()
+                    return items*.v
+                }
+            }
+            assert new Svc().getValues().get() == [1, 2, 3]
+        '''
+    }
+
+    @Test
+    void testAsyncWithElvisOperator() {
+        assertScript '''
+            import java.util.concurrent.CompletableFuture
+
+            class Svc {
+                async fetchOrDefault() {
+                    def v = await CompletableFuture.supplyAsync { null }
+                    return v ?: "default"
+                }
+            }
+            assert new Svc().fetchOrDefault().get() == "default"
+        '''
+    }
+
+    @Test
+    void testAsyncWithSafeNavigation() {
+        assertScript '''
+            class Svc {
+                async nullResult() { null }
+                async safe() {
+                    def r = await nullResult()
+                    return r?.toString() ?: "was-null"
+                }
+            }
+            assert new Svc().safe().get() == "was-null"
+        '''
+    }
+
+    // =====================================================================
+    // 5. Backward compatibility — async/await as identifiers
+    // =====================================================================
+
+    @Test
+    void testAsyncAsLocalVariable() {
+        assertScript '''
+            def async = "value"
+            assert async == "value"
+        '''
+    }
+
+    @Test
+    void testAwaitAsLocalVariable() {
+        assertScript '''
+            def await = 123
+            assert await == 123
+        '''
+    }
+
+    @Test
+    void testAsyncAsMethodNameInClass() {
+        assertScript '''
+            class Legacy {
+                def async() { "legacy-async" }
+                def await() { "legacy-await" }
+            }
+            def obj = new Legacy()
+            assert obj.async() == "legacy-async"
+            assert obj.await() == "legacy-await"
+        '''
+    }
+
+    @Test
+    void testAsyncAsFieldName() {
+        assertScript '''
+            class Config {
+                boolean async = true
+                String await = "ready"
+            }
+            def c = new Config()
+            assert c.async == true
+            assert c.await == "ready"
+        '''
+    }
+
+    @Test
+    void testAsyncAsMapKey() {
+        assertScript '''
+            def map = [async: true, await: false]
+            assert map.async == true
+            assert map.await == false
+        '''
+    }
+
+    @Test
+    void testAsyncAsParameterName() {
+        assertScript '''
+            def process(boolean async) { async ? "yes" : "no" }
+            assert process(true) == "yes"
+        '''
+    }
+
+    @Test
+    void testAsyncAndAwaitAsLabels() {
+        assertScript '''
+            def result = null
+            async:
+            for (i in [1, 2, 3]) {
+                await:
+                for (j in [10, 20]) {
+                    if (j == 20) {
+                        result = i * j
+                        break async
+                    }
+                }
+            }
+            assert result == 20
+        '''
+    }
+
+    // =====================================================================
+    // 6. Error cases — syntax and semantic rejections
+    // =====================================================================
+
+    @Test
+    void testAsyncOnAbstractMethodFails() {
+        shouldFail '''
+            abstract class Svc {
+                @groovy.transform.Async
+                abstract def compute()
+            }
+        '''
+    }
+
+    @Test
+    void testAsyncOnAwaitableReturnTypeFails() {
+        shouldFail '''
+            import groovy.concurrent.Awaitable
+            class Svc {
+                @groovy.transform.Async
+                Awaitable<String> compute() { Awaitable.of("x") }
+            }
+        '''
+    }
+
+    @Test
+    void testForAwaitWithTraditionalForSyntaxFails() {
+        shouldFail '''
+            class Svc {
+                @groovy.transform.Async
+                def work() {
+                    for await (int i = 0; i < 10; i++) {
+                        println i
+                    }
+                }
+            }
+        '''
+    }
+
+    // =====================================================================
+    // 7. 'yield return' — async generator (C#-style async streams)
+    // =====================================================================
+
+    @Test
+    void testYieldReturnBasic() {
+        assertScript '''
+            import groovy.concurrent.AsyncStream
+
+            class Gen {
+                async numbers() {
+                    yield return 1
+                    yield return 2
+                    yield return 3
+                }
+            }
+            def stream = new Gen().numbers()
+            assert stream instanceof AsyncStream
+            def results = []
+            for await (n in stream) {
+                results << n
+            }
+            assert results == [1, 2, 3]
+        '''
+    }
+
+    @Test
+    void testYieldReturnInForLoop() {
+        assertScript '''
+            class Gen {
+                async range(int start, int end) {
+                    for (int i = start; i <= end; i++) {
+                        yield return i
+                    }
+                }
+            }
+            def results = []
+            for await (n in new Gen().range(1, 5)) {
+                results << n
+            }
+            assert results == [1, 2, 3, 4, 5]
+        '''
+    }
+
+    @Test
+    void testYieldReturnInWhileLoop() {
+        assertScript '''
+            class Gen {
+                async countdown(int n) {
+                    while (n > 0) {
+                        yield return n
+                        n--
+                    }
+                }
+            }
+            def results = []
+            for await (v in new Gen().countdown(4)) {
+                results << v
+            }
+            assert results == [4, 3, 2, 1]
+        '''
+    }
+
+    @Test
+    void testYieldReturnInForEach() {
+        assertScript '''
+            class Gen {
+                async transform(List items) {
+                    for (item in items) {
+                        yield return item.toString().toUpperCase()
+                    }
+                }
+            }
+            def results = []
+            for await (s in new Gen().transform(["hello", "world"])) {
+                results << s
+            }
+            assert results == ["HELLO", "WORLD"]
+        '''
+    }
+
+    @Test
+    void testYieldReturnStrings() {
+        assertScript '''
+            class Gen {
+                async greetings() {
+                    yield return "hello"
+                    yield return "world"
+                    yield return "!"
+                }
+            }
+            def results = []
+            for await (s in new Gen().greetings()) {
+                results << s
+            }
+            assert results == ["hello", "world", "!"]
+        '''
+    }
+
+    @Test
+    void testYieldReturnWithGString() {
+        assertScript '''
+            class Gen {
+                async labeled(List items) {
+                    int idx = 0
+                    for (item in items) {
+                        yield return "${idx}: ${item}"
+                        idx++
+                    }
+                }
+            }
+            def results = []
+            for await (s in new Gen().labeled(["a", "b", "c"])) {
+                results << s
+            }
+            assert results == ["0: a", "1: b", "2: c"]
+        '''
+    }
+
+    @Test
+    void testYieldReturnWithFilter() {
+        assertScript '''
+            class Gen {
+                async evens(int max) {
+                    for (int i = 1; i <= max; i++) {
+                        if (i % 2 == 0) {
+                            yield return i
+                        }
+                    }
+                }
+            }
+            def results = []
+            for await (n in new Gen().evens(10)) {
+                results << n
+            }
+            assert results == [2, 4, 6, 8, 10]
+        '''
+    }
+
+    @Test
+    void testYieldReturnWithParameters() {
+        assertScript '''
+            class Gen {
+                async multiply(List<Integer> items, int factor) {
+                    for (item in items) {
+                        yield return item * factor
+                    }
+                }
+            }
+            def results = []
+            for await (n in new Gen().multiply([1, 2, 3], 10)) {
+                results << n
+            }
+            assert results == [10, 20, 30]
+        '''
+    }
+
+    @Test
+    void testYieldReturnEmpty() {
+        assertScript '''
+            class Gen {
+                async empty() {
+                    // A generator must have at least one yield return
+                    // to be detected as a generator. An empty body with
+                    // no yield returns becomes a regular async method.
+                    if (false) yield return "unreachable"
+                }
+            }
+            def results = []
+            for await (item in new Gen().empty()) {
+                results << item
+            }
+            assert results == []
+        '''
+    }
+
+    @Test
+    void testYieldReturnSingleElement() {
+        assertScript '''
+            class Gen {
+                async single() {
+                    yield return "only"
+                }
+            }
+            def results = []
+            for await (item in new Gen().single()) {
+                results << item
+            }
+            assert results == ["only"]
+        '''
+    }
+
+    @Test
+    void testYieldReturnWithConditionalLogic() {
+        assertScript '''
+            class Gen {
+                async fizzBuzz(int n) {
+                    for (int i = 1; i <= n; i++) {
+                        if (i % 15 == 0) yield return "FizzBuzz"
+                        else if (i % 3 == 0) yield return "Fizz"
+                        else if (i % 5 == 0) yield return "Buzz"
+                        else yield return i.toString()
+                    }
+                }
+            }
+            def results = []
+            for await (s in new Gen().fizzBuzz(15)) {
+                results << s
+            }
+            assert results[0] == "1"
+            assert results[2] == "Fizz"
+            assert results[4] == "Buzz"
+            assert results[14] == "FizzBuzz"
+            assert results.size() == 15
+        '''
+    }
+
+    @Test
+    void testYieldReturnConsumerBreaksEarly() {
+        assertScript '''
+            class Gen {
+                async infinite() {
+                    int i = 0
+                    while (true) {
+                        yield return i++
+                    }
+                }
+            }
+            // Consumer only takes first 5
+            def results = []
+            def stream = new Gen().infinite()
+            for await (n in stream) {
+                results << n
+                if (results.size() >= 5) break
+            }
+            assert results == [0, 1, 2, 3, 4]
+        '''
+    }
+
+    @Test
+    void testYieldReturnWithAnnotation() {
+        assertScript '''
+            import groovy.transform.Async
+            import groovy.concurrent.AsyncStream
+
+            class Gen {
+                @Async
+                def items() {
+                    yield return "x"
+                    yield return "y"
+                }
+            }
+            def stream = new Gen().items()
+            assert stream instanceof AsyncStream
+            def results = []
+            for await (s in stream) {
+                results << s
+            }
+            assert results == ["x", "y"]
+        '''
+    }
+
+    @Test
+    void testYieldReturnInScript() {
+        assertScript '''
+            import groovy.concurrent.AsyncStream
+
+            async fibonacci(int count) {
+                int a = 0, b = 1
+                for (int i = 0; i < count; i++) {
+                    yield return a
+                    int temp = a + b
+                    a = b
+                    b = temp
+                }
+            }
+
+            def results = []
+            for await (n in fibonacci(8)) {
+                results << n
+            }
+            assert results == [0, 1, 1, 2, 3, 5, 8, 13]
+        '''
+    }
+
+    @Test
+    void testYieldReturnWithAwaitInsideBody() {
+        assertScript '''
+            import java.util.concurrent.CompletableFuture
+
+            class Gen {
+                async asyncItems() {
+                    for (i in 1..3) {
+                        def v = await CompletableFuture.supplyAsync { i * 100 }
+                        yield return v
+                    }
+                }
+            }
+            def results = []
+            for await (n in new Gen().asyncItems()) {
+                results << n
+            }
+            assert results == [100, 200, 300]
+        '''
+    }
+
+    @Test
+    void testYieldReturnWithTryCatch() {
+        assertScript '''
+            class Gen {
+                async safeItems() {
+                    for (i in 1..5) {
+                        try {
+                            if (i == 3) throw new RuntimeException("skip")
+                            yield return i
+                        } catch (RuntimeException e) {
+                            yield return -1
+                        }
+                    }
+                }
+            }
+            def results = []
+            for await (n in new Gen().safeItems()) {
+                results << n
+            }
+            assert results == [1, 2, -1, 4, 5]
+        '''
+    }
+
+    @Test
+    void testYieldReturnExceptionPropagation() {
+        assertScript '''
+            class Gen {
+                async failing() {
+                    yield return 1
+                    yield return 2
+                    throw new IllegalStateException("generator failed")
+                }
+            }
+            def results = []
+            try {
+                for await (n in new Gen().failing()) {
+                    results << n
+                }
+                assert false : "expected exception"
+            } catch (IllegalStateException e) {
+                assert e.message == "generator failed"
+            }
+            assert results == [1, 2]
+        '''
+    }
+
+    @Test
+    void testYieldReturnNullValues() {
+        assertScript '''
+            class Gen {
+                async withNulls() {
+                    yield return "a"
+                    yield return null
+                    yield return "b"
+                }
+            }
+            def results = []
+            for await (item in new Gen().withNulls()) {
+                results << item
+            }
+            assert results == ["a", null, "b"]
+        '''
+    }
+
+    @Test
+    void testYieldReturnMixedTypes() {
+        assertScript '''
+            class Gen {
+                async mixed() {
+                    yield return 42
+                    yield return "hello"
+                    yield return [1, 2, 3]
+                    yield return true
+                }
+            }
+            def results = []
+            for await (item in new Gen().mixed()) {
+                results << item
+            }
+            assert results[0] == 42
+            assert results[1] == "hello"
+            assert results[2] == [1, 2, 3]
+            assert results[3] == true
+        '''
+    }
+
+    @Test
+    void testYieldReturnForAwaitAccumulation() {
+        assertScript '''
+            class Gen {
+                async items() {
+                    for (i in 1..5) {
+                        yield return i
+                    }
+                }
+            }
+            // Sum using for-await
+            async sumStream() {
+                int total = 0
+                for await (n in new Gen().items()) {
+                    total += n
+                }
+                return total
+            }
+            assert sumStream().get() == 15
+        '''
+    }
+
+    @Test
+    void testYieldReturnChainedGenerators() {
+        assertScript '''
+            class Gen {
+                async source() {
+                    yield return 1
+                    yield return 2
+                    yield return 3
+                }
+
+                async doubled() {
+                    for await (n in source()) {
+                        yield return n * 2
+                    }
+                }
+            }
+            def results = []
+            for await (n in new Gen().doubled()) {
+                results << n
+            }
+            assert results == [2, 4, 6]
+        '''
+    }
+
+    @Test
+    void testYieldReturnWithForAwaitTransformPipeline() {
+        assertScript '''
+            class Pipeline {
+                async numbers(int n) {
+                    for (int i = 1; i <= n; i++) {
+                        yield return i
+                    }
+                }
+
+                async squared(source) {
+                    for await (n in source) {
+                        yield return n * n
+                    }
+                }
+
+                async asStrings(source) {
+                    for await (n in source) {
+                        yield return "val=${n}"
+                    }
+                }
+            }
+            def p = new Pipeline()
+            def results = []
+            for await (s in p.asStrings(p.squared(p.numbers(4)))) {
+                results << s
+            }
+            assert results == ["val=1", "val=4", "val=9", "val=16"]
+        '''
+    }
+
+    @Test
+    void testYieldReturnStaticMethod() {
+        assertScript '''
+            class Gen {
+                async static def staticRange(int n) {
+                    for (int i = 0; i < n; i++) {
+                        yield return i
+                    }
+                }
+            }
+            def results = []
+            for await (n in Gen.staticRange(4)) {
+                results << n
+            }
+            assert results == [0, 1, 2, 3]
+        '''
+    }
+
+    @Test
+    void testYieldReturnWithMapEntries() {
+        assertScript '''
+            class Gen {
+                async entries(Map map) {
+                    for (entry in map.entrySet()) {
+                        yield return [key: entry.key, value: entry.value]
+                    }
+                }
+            }
+            def results = []
+            for await (e in new Gen().entries([a: 1, b: 2, c: 3])) {
+                results << e
+            }
+            assert results.size() == 3
+            assert results.find { it.key == "b" }.value == 2
+        '''
+    }
+
+    @Test
+    void testYieldReturnLargeSequence() {
+        assertScript '''
+            class Gen {
+                async sequence(int count) {
+                    for (int i = 0; i < count; i++) {
+                        yield return i
+                    }
+                }
+            }
+            int sum = 0
+            int count = 0
+            for await (n in new Gen().sequence(1000)) {
+                sum += n
+                count++
+            }
+            assert count == 1000
+            assert sum == (999 * 1000) / 2
+        '''
+    }
+
+    @Test
+    void testYieldReturnOutsideAsyncMethodFails() {
+        assertScript '''
+            // yield return outside async method should fail at runtime
+            try {
+                groovy.transform.AsyncUtils.yieldReturn("oops")
+                assert false : "expected exception"
+            } catch (IllegalStateException e) {
+                assert e.message.contains("yield return")
+            }
+        '''
+    }
+}
diff --git a/src/test/groovy/org/codehaus/groovy/transform/AsyncClosureLambdaTest.groovy b/src/test/groovy/org/codehaus/groovy/transform/AsyncClosureLambdaTest.groovy
new file mode 100644
index 0000000000..7a717cb20b
--- /dev/null
+++ b/src/test/groovy/org/codehaus/groovy/transform/AsyncClosureLambdaTest.groovy
@@ -0,0 +1,793 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.codehaus.groovy.transform
+
+import org.junit.jupiter.api.Test
+
+import static groovy.test.GroovyAssert.assertScript
+
+/**
+ * Tests for async closures and async lambda expressions.
+ * <p>
+ * Syntax:
+ * <ul>
+ *   <li>{@code async { body }} — immediately executes body async, returns Awaitable</li>
+ *   <li>{@code async { x, y -> body }} — creates async closure, returns Closure&lt;Awaitable&gt;</li>
+ *   <li>{@code async (x, y) -> { body }} — creates async lambda, returns Closure&lt;Awaitable&gt;</li>
+ *   <li>Both support {@code yield return} for AsyncStream generation</li>
+ * </ul>
+ *
+ * @since 6.0.0
+ */
+final class AsyncClosureLambdaTest {
+
+    // =========================================================================
+    // 1. Async Closure — immediate execution (no params)
+    // =========================================================================
+
+    @Test
+    void testAsyncClosureImmediateExecution() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+            import groovy.concurrent.Awaitable
+
+            def result = async { 42 }
+            assert result instanceof Awaitable
+            assert await(result) == 42
+        '''
+    }
+
+    @Test
+    void testAsyncClosureWithExplicitEmptyParams() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+            import groovy.concurrent.Awaitable
+
+            def result = async { -> "hello" }
+            assert result instanceof Awaitable
+            assert await(result) == "hello"
+        '''
+    }
+
+    @Test
+    void testAsyncClosureRunsOnSeparateThread() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+
+            def mainThread = Thread.currentThread().name
+            def awaitable = async { Thread.currentThread().name }
+            def asyncThread = await(awaitable)
+            assert asyncThread != mainThread
+        '''
+    }
+
+    @Test
+    void testAsyncClosureWithComputation() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+
+            def awaitable = async {
+                def sum = 0
+                for (i in 1..100) { sum += i }
+                sum
+            }
+            assert await(awaitable) == 5050
+        '''
+    }
+
+    @Test
+    void testAsyncClosureWithAwaitInside() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+
+            @Async
+            def fetchValue() { return 10 }
+
+            def result = async {
+                def v = await(fetchValue())
+                v * 2
+            }
+            assert await(result) == 20
+        '''
+    }
+
+    @Test
+    void testMultipleAsyncClosuresParallel() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+
+            def a = async { Thread.sleep(50); 1 }
+            def b = async { Thread.sleep(50); 2 }
+            def c = async { Thread.sleep(50); 3 }
+
+            def results = awaitAll(a, b, c)
+            assert results == [1, 2, 3]
+        '''
+    }
+
+    @Test
+    void testAsyncClosureAssignedToVariable() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+            import groovy.concurrent.Awaitable
+
+            def task = async { 100 + 200 }
+            // Can use the Awaitable later
+            assert task instanceof Awaitable
+            def val = await(task)
+            assert val == 300
+        '''
+    }
+
+    @Test
+    void testAsyncClosureReturnsNull() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+
+            def result = async { null }
+            assert await(result) == null
+        '''
+    }
+
+    // =========================================================================
+    // 2. Async Closure — with parameters (deferred execution)
+    // =========================================================================
+
+    @Test
+    void testAsyncClosureWithParams() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+
+            def asyncAdd = async { x, y -> x + y }
+            def result = asyncAdd(10, 20)
+            assert await(result) == 30
+        '''
+    }
+
+    @Test
+    void testAsyncClosureWithSingleParam() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+
+            def asyncDouble = async { n -> n * 2 }
+            assert await(asyncDouble(21)) == 42
+        '''
+    }
+
+    @Test
+    void testAsyncClosureWithParamsCalledMultipleTimes() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+
+            def asyncSquare = async { n -> n * n }
+            def r1 = await(asyncSquare(3))
+            def r2 = await(asyncSquare(5))
+            def r3 = await(asyncSquare(7))
+            assert r1 == 9
+            assert r2 == 25
+            assert r3 == 49
+        '''
+    }
+
+    @Test
+    void testAsyncClosureWithParamsIsClosure() {
+        assertScript '''
+            import groovy.concurrent.Awaitable
+
+            def asyncFn = async { x -> x.toUpperCase() }
+            assert asyncFn instanceof Closure
+            def result = asyncFn("hello")
+            assert result instanceof Awaitable
+        '''
+    }
+
+    @Test
+    void testAsyncClosureWithParamsAndStringOps() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+
+            def asyncConcat = async { a, b, sep -> "${a}${sep}${b}" }
+            assert await(asyncConcat("hello", "world", " ")) == "hello world"
+        '''
+    }
+
+    @Test
+    void testAsyncClosurePassedAsArgument() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+
+            def applyAsync(fn, x) {
+                return await(fn(x))
+            }
+
+            def asyncInc = async { n -> n + 1 }
+            assert applyAsync(asyncInc, 41) == 42
+        '''
+    }
+
+    @Test
+    void testAsyncClosureStoredInCollection() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+
+            def ops = [
+                add: async { a, b -> a + b },
+                mul: async { a, b -> a * b },
+                sub: async { a, b -> a - b }
+            ]
+
+            assert await(ops.add(3, 4)) == 7
+            assert await(ops.mul(3, 4)) == 12
+            assert await(ops.sub(10, 4)) == 6
+        '''
+    }
+
+    // =========================================================================
+    // 3. Async Lambda Expression
+    // =========================================================================
+
+    @Test
+    void testAsyncLambdaWithSingleParam() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+
+            def asyncDouble = async (n) -> { n * 2 }
+            assert await(asyncDouble(21)) == 42
+        '''
+    }
+
+    @Test
+    void testAsyncLambdaWithMultipleParams() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+
+            def asyncAdd = async (a, b) -> { a + b }
+            assert await(asyncAdd(10, 32)) == 42
+        '''
+    }
+
+    @Test
+    void testAsyncLambdaWithSingleExpression() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+
+            def asyncInc = async (n) -> n + 1
+            assert await(asyncInc(41)) == 42
+        '''
+    }
+
+    @Test
+    void testAsyncLambdaIsClosure() {
+        assertScript '''
+            import groovy.concurrent.Awaitable
+
+            def fn = async (x) -> { x * x }
+            assert fn instanceof Closure
+            assert fn(5) instanceof Awaitable
+        '''
+    }
+
+    @Test
+    void testAsyncLambdaCalledMultipleTimes() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+
+            def factorial = async (n) -> {
+                def result = 1
+                for (i in 2..n) { if (n >= 2) result *= i }
+                result
+            }
+
+            assert await(factorial(5)) == 120
+            assert await(factorial(6)) == 720
+            assert await(factorial(10)) == 3628800
+        '''
+    }
+
+    // =========================================================================
+    // 4. Async Closure with yield return (AsyncStream generator)
+    // =========================================================================
+
+    @Test
+    void testAsyncClosureGeneratorImmediate() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+            import groovy.concurrent.AsyncStream
+
+            def stream = async {
+                yield return 1
+                yield return 2
+                yield return 3
+            }
+            assert stream instanceof AsyncStream
+
+            def items = []
+            while (await(stream.moveNext())) {
+                items << stream.current
+            }
+            assert items == [1, 2, 3]
+        '''
+    }
+
+    @Test
+    void testAsyncClosureGeneratorWithForAwait() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+
+            @Async
+            def collect() {
+                def stream = async {
+                    yield return "a"
+                    yield return "b"
+                    yield return "c"
+                }
+                def items = []
+                for await (item in stream) {
+                    items << item
+                }
+                return items
+            }
+
+            assert await(collect()) == ["a", "b", "c"]
+        '''
+    }
+
+    @Test
+    void testAsyncClosureGeneratorWithParams() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+
+            @Async
+            def collect() {
+                def range = async { start, end ->
+                    for (i in start..end) {
+                        yield return i
+                    }
+                }
+                def items = []
+                for await (item in range(1, 5)) {
+                    items << item
+                }
+                return items
+            }
+
+            assert await(collect()) == [1, 2, 3, 4, 5]
+        '''
+    }
+
+    @Test
+    void testAsyncClosureGeneratorWithParamsCalledMultipleTimes() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+
+            @Async
+            def test() {
+                def repeat = async { value, times ->
+                    for (i in 1..times) {
+                        yield return value
+                    }
+                }
+                def r1 = []
+                for await (item in repeat("x", 3)) { r1 << item }
+                def r2 = []
+                for await (item in repeat("y", 2)) { r2 << item }
+                return [r1, r2]
+            }
+
+            def result = await(test())
+            assert result[0] == ["x", "x", "x"]
+            assert result[1] == ["y", "y"]
+        '''
+    }
+
+    // =========================================================================
+    // 5. Async Lambda with yield return
+    // =========================================================================
+
+    @Test
+    void testAsyncLambdaGeneratorWithForAwait() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+
+            @Async
+            def collect() {
+                def squares = async (n) -> {
+                    for (i in 1..n) {
+                        yield return i * i
+                    }
+                }
+                def items = []
+                for await (item in squares(4)) {
+                    items << item
+                }
+                return items
+            }
+
+            assert await(collect()) == [1, 4, 9, 16]
+        '''
+    }
+
+    // =========================================================================
+    // 6. Exception handling in async closures/lambdas
+    // =========================================================================
+
+    @Test
+    void testAsyncClosureExceptionTransparency() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+
+            @Async
+            def caller() {
+                def task = async { throw new java.io.IOException("async closure error") }
+                try {
+                    await task
+                    assert false
+                } catch (java.io.IOException e) {
+                    return e.message
+                }
+            }
+
+            assert await(caller()) == "async closure error"
+        '''
+    }
+
+    @Test
+    void testAsyncClosureWithParamsExceptionTransparency() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+
+            @Async
+            def caller() {
+                def asyncDiv = async { a, b ->
+                    if (b == 0) throw new ArithmeticException("division by zero")
+                    a / b
+                }
+                try {
+                    await asyncDiv(10, 0)
+                    assert false
+                } catch (ArithmeticException e) {
+                    return e.message
+                }
+            }
+
+            assert await(caller()) == "division by zero"
+        '''
+    }
+
+    @Test
+    void testAsyncLambdaExceptionHandling() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+
+            @Async
+            def caller() {
+                def failOp = async (msg) -> { throw new IllegalStateException(msg) }
+                try {
+                    await failOp("lambda error")
+                    assert false
+                } catch (IllegalStateException e) {
+                    return e.message
+                }
+            }
+
+            assert await(caller()) == "lambda error"
+        '''
+    }
+
+    @Test
+    void testAsyncClosureGeneratorExceptionPropagation() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+
+            @Async
+            def caller() {
+                def stream = async {
+                    yield return 1
+                    throw new java.io.IOException("generator error")
+                }
+                def items = []
+                try {
+                    for await (item in stream) {
+                        items << item
+                    }
+                    assert false
+                } catch (java.io.IOException e) {
+                    return "${items}:${e.message}"
+                }
+            }
+
+            assert await(caller()) == "[1]:generator error"
+        '''
+    }
+
+    // =========================================================================
+    // 7. Async closure/lambda used with await syntax
+    // =========================================================================
+
+    @Test
+    void testAwaitAsyncClosureInAsyncMethod() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+
+            @Async
+            def compute() {
+                def a = await async { 10 }
+                def b = await async { 20 }
+                return a + b
+            }
+
+            assert await(compute()) == 30
+        '''
+    }
+
+    @Test
+    void testAwaitAsyncClosureWithParamsInAsyncMethod() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+
+            @Async
+            def compute() {
+                def multiply = async { x, y -> x * y }
+                def a = await multiply(6, 7)
+                return a
+            }
+
+            assert await(compute()) == 42
+        '''
+    }
+
+    @Test
+    void testAsyncClosuresComposed() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+
+            @Async
+            def pipeline() {
+                def step1 = async { 10 }
+                def v1 = await step1
+
+                def step2 = async { v1 * 2 }
+                def v2 = await step2
+
+                def step3 = async { v2 + 1 }
+                return await step3
+            }
+
+            assert await(pipeline()) == 21
+        '''
+    }
+
+    // =========================================================================
+    // 8. Async closure/lambda with complex types
+    // =========================================================================
+
+    @Test
+    void testAsyncClosureWithListResult() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+
+            def task = async { [1, 2, 3].collect { it * it } }
+            assert await(task) == [1, 4, 9]
+        '''
+    }
+
+    @Test
+    void testAsyncClosureWithMapResult() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+
+            def task = async { [name: "Groovy", version: 6] }
+            def result = await(task)
+            assert result.name == "Groovy"
+            assert result.version == 6
+        '''
+    }
+
+    @Test
+    void testAsyncClosureWithStringInterpolation() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+
+            def greet = async { name -> "Hello, ${name}!" }
+            assert await(greet("World")) == "Hello, World!"
+        '''
+    }
+
+    // =========================================================================
+    // 9. Async closure/lambda used with awaitAll/awaitAny
+    // =========================================================================
+
+    @Test
+    void testAwaitAllWithAsyncClosures() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+
+            def results = awaitAll(
+                async { 1 },
+                async { 2 },
+                async { 3 }
+            )
+            assert results == [1, 2, 3]
+        '''
+    }
+
+    @Test
+    void testAwaitAnyWithAsyncClosures() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+
+            def result = awaitAny(
+                async { Thread.sleep(100); "slow" },
+                async { "fast" }
+            )
+            // "fast" should usually win, but either is valid
+            assert result == "fast" || result == "slow"
+        '''
+    }
+
+    @Test
+    void testAwaitAllSettledWithAsyncClosures() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+
+            def results = awaitAllSettled(
+                async { 42 },
+                async { throw new RuntimeException("fail") }
+            )
+            assert results[0].isSuccess()
+            assert results[0].value == 42
+            assert results[1].isFailure()
+            assert results[1].error instanceof RuntimeException
+        '''
+    }
+
+    // =========================================================================
+    // 10. Edge cases
+    // =========================================================================
+
+    @Test
+    void testAsyncClosureCapturingOuterVariable() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+
+            def x = 100
+            def task = async { x + 1 }
+            assert await(task) == 101
+        '''
+    }
+
+    @Test
+    void testAsyncClosureWithParamCapturingOuter() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+
+            def base = 1000
+            def asyncAdd = async { n -> base + n }
+            assert await(asyncAdd(42)) == 1042
+        '''
+    }
+
+    @Test
+    void testNestedAsyncClosures() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+
+            def outer = async {
+                def inner = async { 42 }
+                await(inner)
+            }
+            assert await(outer) == 42
+        '''
+    }
+
+    @Test
+    void testAsyncClosureInLoopBody() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+
+            def tasks = (1..5).collect { n -> async { n * n } }
+            def results = awaitAll(tasks as Object[])
+            assert results == [1, 4, 9, 16, 25]
+        '''
+    }
+
+    @Test
+    void testAsyncClosureWithMethodCallChaining() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+            import groovy.concurrent.Awaitable
+
+            def task = async { "hello world" }
+            assert task instanceof Awaitable
+            def chained = task.then { it.toUpperCase() }
+            assert await(chained) == "HELLO WORLD"
+        '''
+    }
+
+    @Test
+    void testAsyncClosureWithExceptionally() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+
+            def task = async { throw new RuntimeException("boom") }
+            def recovered = task.exceptionally { err -> "recovered: ${err.message}" }
+            assert await(recovered) == "recovered: boom"
+        '''
+    }
+
+    @Test
+    void testAsyncClosureGeneratorFactoryPattern() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+
+            @Async
+            def collect() {
+                // Factory that creates fibonacci async streams
+                def fibonacci = async { limit ->
+                    def a = 0, b = 1
+                    for (i in 0..<limit) {
+                        yield return a
+                        def temp = a + b
+                        a = b
+                        b = temp
+                    }
+                }
+                def items = []
+                for await (item in fibonacci(8)) {
+                    items << item
+                }
+                return items
+            }
+
+            assert await(collect()) == [0, 1, 1, 2, 3, 5, 8, 13]
+        '''
+    }
+
+    @Test
+    void testAsyncClosureCancellation() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+            import java.util.concurrent.CancellationException
+
+            def task = async { Thread.sleep(10_000); 42 }
+            task.cancel()
+            assert task.isCancelled()
+            assert task.isCompletedExceptionally()
+            try {
+                await(task)
+                assert false
+            } catch (CancellationException e) {
+                // expected
+            }
+        '''
+    }
+}
diff --git a/src/test/groovy/org/codehaus/groovy/transform/AsyncExceptionHandlingTest.groovy b/src/test/groovy/org/codehaus/groovy/transform/AsyncExceptionHandlingTest.groovy
new file mode 100644
index 0000000000..e63783ee73
--- /dev/null
+++ b/src/test/groovy/org/codehaus/groovy/transform/AsyncExceptionHandlingTest.groovy
@@ -0,0 +1,1161 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.codehaus.groovy.transform
+
+import org.junit.jupiter.api.Test
+
+import static groovy.test.GroovyAssert.assertScript
+
+/**
+ * Comprehensive exception handling tests for async/await, covering:
+ * <ol>
+ *   <li>Exception transparency — checked exceptions pass through {@code await}
+ *       without wrapping (like C#/Kotlin/JavaScript)</li>
+ *   <li>Deep unwrapping — nested CompletionException/ExecutionException chains</li>
+ *   <li>Cancellation support — {@code cancel()}/{@code isCancelled()}</li>
+ *   <li>{@code awaitAll}/{@code awaitAny} with mixed success/failure</li>
+ *   <li>{@code awaitAllSettled} — JavaScript's {@code Promise.allSettled()}</li>
+ *   <li>Generator (yield return) exception propagation</li>
+ *   <li>Exception chaining across multiple await calls</li>
+ *   <li>try/catch/finally in async methods</li>
+ * </ol>
+ *
+ * @since 6.0.0
+ */
+final class AsyncExceptionHandlingTest {
+
+    // =========================================================================
+    // 1. Exception Transparency — checked exceptions through await
+    // =========================================================================
+
+    @Test
+    void testAwaitIOExceptionTransparency() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+
+            async fetchData() {
+                throw new java.io.IOException("disk failure")
+            }
+
+            async caller() {
+                try {
+                    await fetchData()
+                    assert false : "should not reach"
+                } catch (java.io.IOException e) {
+                    return e.message
+                }
+            }
+
+            assert await(caller()) == "disk failure"
+        '''
+    }
+
+    @Test
+    void testAwaitSQLExceptionTransparency() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+
+            async queryDb() {
+                throw new java.sql.SQLException("connection refused", "08001")
+            }
+
+            async caller() {
+                try {
+                    await queryDb()
+                    assert false : "should not reach"
+                } catch (java.sql.SQLException e) {
+                    return e.SQLState
+                }
+            }
+
+            assert await(caller()) == "08001"
+        '''
+    }
+
+    @Test
+    void testAwaitCustomCheckedExceptionTransparency() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+
+            class BusinessException extends Exception {
+                int code
+                BusinessException(String msg, int code) {
+                    super(msg)
+                    this.code = code
+                }
+            }
+
+            async process() {
+                throw new BusinessException("insufficient funds", 402)
+            }
+
+            async caller() {
+                try {
+                    await process()
+                    assert false
+                } catch (BusinessException e) {
+                    return "${e.message}:${e.code}"
+                }
+            }
+
+            assert await(caller()) == "insufficient funds:402"
+        '''
+    }
+
+    @Test
+    void testAwaitRuntimeExceptionTransparency() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+
+            async badOp() {
+                throw new IllegalArgumentException("bad arg")
+            }
+
+            async caller() {
+                try {
+                    await badOp()
+                    assert false
+                } catch (IllegalArgumentException e) {
+                    return e.message
+                }
+            }
+
+            assert await(caller()) == "bad arg"
+        '''
+    }
+
+    @Test
+    void testAwaitErrorPropagation() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+
+            async oom() {
+                throw new OutOfMemoryError("test OOM")
+            }
+
+            async caller() {
+                try {
+                    await oom()
+                    assert false
+                } catch (OutOfMemoryError e) {
+                    return e.message
+                }
+            }
+
+            assert await(caller()) == "test OOM"
+        '''
+    }
+
+    // =========================================================================
+    // 2. Deep Unwrapping — nested wrapper chains
+    // =========================================================================
+
+    @Test
+    void testDeepUnwrapCompletionException() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+            import java.util.concurrent.CompletableFuture
+            import java.util.concurrent.CompletionException
+
+            // Simulate deeply nested wrapping
+            async caller() {
+                def original = new java.io.FileNotFoundException("config.yml")
+                def wrapped = new CompletionException(new java.util.concurrent.ExecutionException(original))
+                def future = new CompletableFuture()
+                future.completeExceptionally(wrapped)
+                try {
+                    await future
+                    assert false
+                } catch (java.io.FileNotFoundException e) {
+                    return e.message
+                }
+            }
+
+            assert await(caller()) == "config.yml"
+        '''
+    }
+
+    @Test
+    void testDeepUnwrapExecutionException() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+            import java.util.concurrent.CompletableFuture
+            import java.util.concurrent.CompletionException
+            import java.util.concurrent.ExecutionException
+
+            async caller() {
+                def original = new IllegalStateException("deep cause")
+                // Triple nesting: CompletionException -> ExecutionException -> CompletionException -> actual
+                def inner = new CompletionException(original)
+                def middle = new ExecutionException(inner)
+                def outer = new CompletionException(middle)
+                def future = new CompletableFuture()
+                future.completeExceptionally(outer)
+                try {
+                    await future
+                    assert false
+                } catch (IllegalStateException e) {
+                    return e.message
+                }
+            }
+
+            assert await(caller()) == "deep cause"
+        '''
+    }
+
+    // =========================================================================
+    // 3. Cancellation Support
+    // =========================================================================
+
+    @Test
+    void testCancelAwaitable() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+            import java.util.concurrent.CancellationException
+
+            async slowTask() {
+                Thread.sleep(10_000)
+                return 42
+            }
+
+            def task = slowTask()
+            assert !task.isCancelled()
+            assert !task.isDone()
+
+            boolean cancelled = task.cancel()
+            assert cancelled || task.isDone()
+
+            assert task.isCancelled()
+            assert task.isDone()
+            assert task.isCompletedExceptionally()
+
+            try {
+                await(task)
+                assert false : "should throw CancellationException"
+            } catch (CancellationException e) {
+                // expected — matches C#/Kotlin cancellation behavior
+            }
+        '''
+    }
+
+    @Test
+    void testCancelAlreadyCompletedIsNoOp() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+            import groovy.concurrent.Awaitable
+
+            def result = Awaitable.of(42)
+            assert !result.cancel()
+            assert !result.isCancelled()
+            assert result.isDone()
+            assert !result.isCompletedExceptionally()
+            assert await(result) == 42
+        '''
+    }
+
+    @Test
+    void testIsCompletedExceptionallyOnFailure() {
+        assertScript '''
+            import groovy.concurrent.Awaitable
+
+            def failed = Awaitable.failed(new RuntimeException("boom"))
+            assert failed.isDone()
+            assert failed.isCompletedExceptionally()
+            assert !failed.isCancelled()
+        '''
+    }
+
+    // =========================================================================
+    // 4. awaitAll with mixed success/failure
+    // =========================================================================
+
+    @Test
+    void testAwaitAllWithFailureThrowsOriginal() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+
+            async success() { return 1 }
+
+            async failure() { throw new java.io.IOException("network error") }
+
+            async caller() {
+                try {
+                    awaitAll(success(), failure())
+                    assert false
+                } catch (java.io.IOException e) {
+                    return e.message
+                }
+            }
+
+            assert await(caller()) == "network error"
+        '''
+    }
+
+    @Test
+    void testAwaitAllWithAwaitableObjects() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+            import groovy.concurrent.Awaitable
+
+            async a() { return 10 }
+
+            async b() { return 20 }
+
+            async caller() {
+                def results = awaitAll(a(), b(), Awaitable.of(30))
+                return results
+            }
+
+            def r = await(caller())
+            assert r == [10, 20, 30]
+        '''
+    }
+
+    @Test
+    void testAwaitAnyWithFailure() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+            import groovy.concurrent.Awaitable
+
+            async caller() {
+                try {
+                    awaitAny(Awaitable.failed(new java.io.IOException("fail")))
+                    assert false
+                } catch (java.io.IOException e) {
+                    return e.message
+                }
+            }
+
+            assert await(caller()) == "fail"
+        '''
+    }
+
+    @Test
+    void testAwaitAnyWithMixedSourceTypes() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+            import groovy.concurrent.Awaitable
+            import java.util.concurrent.CompletableFuture
+
+            async caller() {
+                def cf = CompletableFuture.completedFuture(42)
+                def aw = Awaitable.of(99)
+                def result = awaitAny(cf, aw)
+                return result
+            }
+
+            def r = await(caller())
+            assert r == 42 || r == 99
+        '''
+    }
+
+    // =========================================================================
+    // 5. awaitAllSettled — Promise.allSettled() equivalent
+    // =========================================================================
+
+    @Test
+    void testAwaitAllSettledMixedResults() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+            import groovy.concurrent.Awaitable
+
+            async caller() {
+                def a = Awaitable.of(1)
+                def b = Awaitable.failed(new java.io.IOException("err"))
+                def c = Awaitable.of(3)
+                return awaitAllSettled(a, b, c)
+            }
+
+            def results = await(caller())
+            assert results.size() == 3
+
+            assert results[0].isSuccess()
+            assert results[0].value == 1
+
+            assert results[1].isFailure()
+            assert results[1].error instanceof java.io.IOException
+            assert results[1].error.message == "err"
+
+            assert results[2].isSuccess()
+            assert results[2].value == 3
+        '''
+    }
+
+    @Test
+    void testAwaitAllSettledAllSuccess() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+            import groovy.concurrent.Awaitable
+
+            async caller() {
+                return awaitAllSettled(
+                    Awaitable.of("a"), Awaitable.of("b"), Awaitable.of("c")
+                )
+            }
+
+            def results = await(caller())
+            assert results.every { it.isSuccess() }
+            assert results*.value == ["a", "b", "c"]
+        '''
+    }
+
+    @Test
+    void testAwaitAllSettledAllFailure() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+            import groovy.concurrent.Awaitable
+
+            async caller() {
+                return awaitAllSettled(
+                    Awaitable.failed(new RuntimeException("e1")),
+                    Awaitable.failed(new java.io.IOException("e2")),
+                    Awaitable.failed(new IllegalStateException("e3"))
+                )
+            }
+
+            def results = await(caller())
+            assert results.every { it.isFailure() }
+            assert results[0].error instanceof RuntimeException
+            assert results[1].error instanceof java.io.IOException
+            assert results[2].error instanceof IllegalStateException
+        '''
+    }
+
+    @Test
+    void testAwaitAllSettledGetOrElse() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+            import groovy.concurrent.Awaitable
+
+            async caller() {
+                def results = awaitAllSettled(
+                    Awaitable.of(10),
+                    Awaitable.failed(new RuntimeException("oops"))
+                )
+                return results.collect { it.getOrElse { err -> -1 } }
+            }
+
+            assert await(caller()) == [10, -1]
+        '''
+    }
+
+    @Test
+    void testAwaitAllSettledNeverThrows() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+            import groovy.concurrent.Awaitable
+
+            async caller() {
+                // Should NOT throw even though all fail
+                def results = awaitAllSettled(
+                    Awaitable.failed(new Error("err1")),
+                    Awaitable.failed(new RuntimeException("err2"))
+                )
+                return results.size()
+            }
+
+            assert await(caller()) == 2
+        '''
+    }
+
+    @Test
+    void testAwaitAllSettledWithCancelledTask() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+            import groovy.concurrent.Awaitable
+            import java.util.concurrent.CancellationException
+            import java.util.concurrent.CompletableFuture
+
+            async caller() {
+                def cf = new CompletableFuture()
+                cf.cancel(true)
+                def results = awaitAllSettled(
+                    Awaitable.of(1), cf
+                )
+                assert results[0].isSuccess()
+                assert results[0].value == 1
+                assert results[1].isFailure()
+                assert results[1].error instanceof CancellationException
+                return "ok"
+            }
+
+            assert await(caller()) == "ok"
+        '''
+    }
+
+    // =========================================================================
+    // 6. Generator (yield return) exception propagation
+    // =========================================================================
+
+    @Test
+    void testGeneratorCheckedExceptionPropagation() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+
+            async failingGenerator() {
+                yield return 1
+                yield return 2
+                throw new java.io.IOException("generator IO error")
+            }
+
+            async caller() {
+                def collected = []
+                try {
+                    for await (item in failingGenerator()) {
+                        collected << item
+                    }
+                    assert false : "should not complete normally"
+                } catch (java.io.IOException e) {
+                    return "${collected}:${e.message}"
+                }
+            }
+
+            assert await(caller()) == "[1, 2]:generator IO error"
+        '''
+    }
+
+    @Test
+    void testGeneratorRuntimeExceptionPropagation() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+
+            async failingGenerator() {
+                yield return "a"
+                throw new IllegalArgumentException("bad value")
+            }
+
+            async caller() {
+                def items = []
+                try {
+                    for await (item in failingGenerator()) {
+                        items << item
+                    }
+                    assert false
+                } catch (IllegalArgumentException e) {
+                    return "${items}:${e.message}"
+                }
+            }
+
+            assert await(caller()) == "[a]:bad value"
+        '''
+    }
+
+    // =========================================================================
+    // 7. try/catch/finally in async methods
+    // =========================================================================
+
+    @Test
+    void testTryCatchFinallyInAsync() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+
+            async riskyOp() {
+                throw new java.io.IOException("test")
+            }
+
+            async caller() {
+                def log = []
+                try {
+                    log << "try"
+                    await riskyOp()
+                    log << "after-await"
+                } catch (java.io.IOException e) {
+                    log << "catch:${e.message}"
+                } finally {
+                    log << "finally"
+                }
+                return log.join(",")
+            }
+
+            assert await(caller()) == "try,catch:test,finally"
+        '''
+    }
+
+    @Test
+    void testMultipleCatchBlocks() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+            import groovy.concurrent.Awaitable
+
+            async throwIO() { throw new java.io.IOException("io") }
+
+            async throwArg() { throw new IllegalArgumentException("arg") }
+
+            async caller(boolean useIO) {
+                try {
+                    if (useIO) {
+                        await throwIO()
+                    } else {
+                        await throwArg()
+                    }
+                    return "none"
+                } catch (java.io.IOException e) {
+                    return "io:${e.message}"
+                } catch (IllegalArgumentException e) {
+                    return "arg:${e.message}"
+                }
+            }
+
+            assert await(caller(true)) == "io:io"
+            assert await(caller(false)) == "arg:arg"
+        '''
+    }
+
+    @Test
+    void testNestedTryCatch() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+
+            async fail1() { throw new java.io.IOException("first") }
+
+            async fail2() { throw new IllegalStateException("second") }
+
+            async caller() {
+                def results = []
+                try {
+                    try {
+                        await fail1()
+                    } catch (java.io.IOException e) {
+                        results << "inner:${e.message}"
+                    }
+                    await fail2()
+                } catch (IllegalStateException e) {
+                    results << "outer:${e.message}"
+                }
+                return results.join(",")
+            }
+
+            assert await(caller()) == "inner:first,outer:second"
+        '''
+    }
+
+    @Test
+    void testFinallyRunsOnSuccess() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+            import groovy.concurrent.Awaitable
+
+            async caller() {
+                def log = []
+                try {
+                    def v = await Awaitable.of(42)
+                    log << "value:$v"
+                } finally {
+                    log << "finally"
+                }
+                return log.join(",")
+            }
+
+            assert await(caller()) == "value:42,finally"
+        '''
+    }
+
+    // =========================================================================
+    // 8. Exception chaining across multiple awaits
+    // =========================================================================
+
+    @Test
+    void testExceptionChainingThreeDeep() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+
+            async level3() {
+                throw new java.io.IOException("root cause")
+            }
+
+            async level2() {
+                return await level3()
+            }
+
+            async level1() {
+                return await level2()
+            }
+
+            async caller() {
+                try {
+                    await level1()
+                    assert false
+                } catch (java.io.IOException e) {
+                    return e.message
+                }
+            }
+
+            assert await(caller()) == "root cause"
+        '''
+    }
+
+    @Test
+    void testExceptionTransformAcrossMultipleAwaits() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+
+            async step1() {
+                throw new java.io.IOException("step1 failed")
+            }
+
+            async step2() {
+                try {
+                    await step1()
+                } catch (java.io.IOException e) {
+                    throw new IllegalStateException("step2 wraps: " + e.message, e)
+                }
+            }
+
+            async caller() {
+                try {
+                    await step2()
+                    assert false
+                } catch (IllegalStateException e) {
+                    assert e.cause instanceof java.io.IOException
+                    return e.message
+                }
+            }
+
+            assert await(caller()) == "step2 wraps: step1 failed"
+        '''
+    }
+
+    // =========================================================================
+    // 9. Awaitable.exceptionally — error recovery
+    // =========================================================================
+
+    @Test
+    void testExceptionallyRecovery() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+            import groovy.concurrent.Awaitable
+
+            async failing() {
+                throw new java.io.IOException("network error")
+            }
+
+            async caller() {
+                def task = failing().exceptionally { err ->
+                    assert err instanceof java.io.IOException
+                    return "recovered: ${err.message}"
+                }
+                return await task
+            }
+
+            assert await(caller()) == "recovered: network error"
+        '''
+    }
+
+    @Test
+    void testExceptionallySeesUnwrappedCause() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+            import groovy.concurrent.Awaitable
+            import java.util.concurrent.CompletableFuture
+            import java.util.concurrent.CompletionException
+
+            async caller() {
+                // Create a future that wraps a checked exception
+                def cf = new CompletableFuture()
+                cf.completeExceptionally(
+                    new CompletionException(new java.io.IOException("wrapped"))
+                )
+                def task = new groovy.concurrent.GroovyPromise(cf)
+                def recovered = task.exceptionally { err ->
+                    // Should receive the IOException, not CompletionException
+                    return err.class.simpleName + ":" + err.message
+                }
+                return await recovered
+            }
+
+            assert await(caller()) == "IOException:wrapped"
+        '''
+    }
+
+    // =========================================================================
+    // 10. Awaitable.then — exception propagation
+    // =========================================================================
+
+    @Test
+    void testThenDoesNotRunOnFailure() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+            import groovy.concurrent.Awaitable
+
+            async caller() {
+                def task = Awaitable.failed(new RuntimeException("boom"))
+                def mapped = task.then { v -> v * 2 }
+                try {
+                    await mapped
+                    assert false
+                } catch (RuntimeException e) {
+                    return e.message
+                }
+            }
+
+            assert await(caller()) == "boom"
+        '''
+    }
+
+    // =========================================================================
+    // 11. Direct await syntax with try/catch — no annotations
+    // =========================================================================
+
+    @Test
+    void testDirectAsyncClosureExceptionHandling() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+
+            async caller() {
+                def task = async {
+                    throw new java.io.IOException("from closure")
+                }
+                try {
+                    await task
+                    assert false
+                } catch (java.io.IOException e) {
+                    return e.message
+                }
+            }
+
+            assert groovy.transform.AsyncUtils.await(caller()) == "from closure"
+        '''
+    }
+
+    // =========================================================================
+    // 12. AwaitResult API edge cases
+    // =========================================================================
+
+    @Test
+    void testAwaitResultAccessors() {
+        assertScript '''
+            import groovy.concurrent.AwaitResult
+
+            def s = AwaitResult.success(42)
+            assert s.isSuccess()
+            assert !s.isFailure()
+            assert s.value == 42
+            try {
+                s.error
+                assert false
+            } catch (IllegalStateException e) {
+                // expected
+            }
+
+            def f = AwaitResult.failure(new RuntimeException("err"))
+            assert f.isFailure()
+            assert !f.isSuccess()
+            assert f.error.message == "err"
+            try {
+                f.value
+                assert false
+            } catch (IllegalStateException e) {
+                // expected
+            }
+        '''
+    }
+
+    @Test
+    void testAwaitResultToString() {
+        assertScript '''
+            import groovy.concurrent.AwaitResult
+
+            def s = AwaitResult.success("hello")
+            assert s.toString() == "AwaitResult.Success[hello]"
+
+            def f = AwaitResult.failure(new RuntimeException("boom"))
+            assert f.toString().startsWith("AwaitResult.Failure[")
+        '''
+    }
+
+    // =========================================================================
+    // 13. InterruptedException → CancellationException
+    // =========================================================================
+
+    @Test
+    void testInterruptedAwaitThrowsCancellation() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+            import java.util.concurrent.CancellationException
+            import groovy.concurrent.Awaitable
+
+            async caller() {
+                def task = Awaitable.of(42)
+                // Interrupt current thread before await
+                Thread.currentThread().interrupt()
+                try {
+                    await task
+                    // Awaitable.of is already complete, so get() may succeed
+                    // (depending on impl). Clear interrupt flag.
+                    Thread.interrupted()
+                    return "completed"
+                } catch (CancellationException e) {
+                    Thread.interrupted() // clear flag
+                    return "cancelled"
+                }
+            }
+
+            def result = await(caller())
+            assert result == "completed" || result == "cancelled"
+        '''
+    }
+
+    // =========================================================================
+    // 14. Exception in for-await loop body
+    // =========================================================================
+
+    @Test
+    void testExceptionInForAwaitBody() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+
+            async generate() {
+                yield return 1
+                yield return 2
+                yield return 3
+            }
+
+            async caller() {
+                def collected = []
+                try {
+                    for await (item in generate()) {
+                        collected << item
+                        if (item == 2) throw new IllegalStateException("stop at 2")
+                    }
+                    assert false
+                } catch (IllegalStateException e) {
+                    return "${collected}:${e.message}"
+                }
+            }
+
+            assert await(caller()) == "[1, 2]:stop at 2"
+        '''
+    }
+
+    // =========================================================================
+    // 15. Async method returning failed Awaitable via exceptionally
+    // =========================================================================
+
+    @Test
+    void testRecoverAndContinue() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+            import groovy.concurrent.Awaitable
+
+            async fetchWithRetry() {
+                def attempt1 = Awaitable.failed(new java.io.IOException("timeout"))
+                def result1 = attempt1.exceptionally { err -> "fallback" }
+                return await result1
+            }
+
+            assert await(fetchWithRetry()) == "fallback"
+        '''
+    }
+
+    // =========================================================================
+    // 16. deepUnwrap exposed via AsyncUtils (package-private, test via reflection or direct)
+    // =========================================================================
+
+    @Test
+    void testDeepUnwrapUtility() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+            import java.util.concurrent.CompletionException
+            import java.util.concurrent.ExecutionException
+
+            def original = new java.io.IOException("root")
+            def l1 = new ExecutionException(original)
+            def l2 = new CompletionException(l1)
+            def l3 = new CompletionException(l2)
+
+            // Access package-private method via Groovy's meta
+            def unwrapped = deepUnwrap(l3)
+            assert unwrapped.is(original)
+        '''
+    }
+
+    @Test
+    void testDeepUnwrapStopsAtNonWrapper() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+
+            def ex = new IllegalArgumentException("not wrapped")
+            assert deepUnwrap(ex).is(ex)
+        '''
+    }
+
+    @Test
+    void testDeepUnwrapHandlesNullCause() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+            import java.util.concurrent.CompletionException
+
+            // CompletionException with null cause
+            def ce = new CompletionException((Throwable) null)
+            def result = deepUnwrap(ce)
+            assert result.is(ce)
+        '''
+    }
+
+    // =========================================================================
+    // 17. awaitAllSettled with CompletableFuture mix
+    // =========================================================================
+
+    @Test
+    void testAwaitAllSettledWithCompletableFutures() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+            import java.util.concurrent.CompletableFuture
+
+            async caller() {
+                def cf1 = CompletableFuture.completedFuture("ok")
+                def cf2 = CompletableFuture.failedFuture(new RuntimeException("bad"))
+                def results = awaitAllSettled(cf1, cf2)
+
+                assert results[0].isSuccess()
+                assert results[0].value == "ok"
+                assert results[1].isFailure()
+                assert results[1].error instanceof RuntimeException
+                return "verified"
+            }
+
+            assert await(caller()) == "verified"
+        '''
+    }
+
+    // =========================================================================
+    // 18. Exception with null message
+    // =========================================================================
+
+    @Test
+    void testExceptionWithNullMessage() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+
+            async throwNull() {
+                throw new RuntimeException((String) null)
+            }
+
+            async caller() {
+                try {
+                    await throwNull()
+                    assert false
+                } catch (RuntimeException e) {
+                    assert e.message == null
+                    return "caught-null-msg"
+                }
+            }
+
+            assert await(caller()) == "caught-null-msg"
+        '''
+    }
+
+    // =========================================================================
+    // 19. Multiple sequential awaits with different exception types
+    // =========================================================================
+
+    @Test
+    void testSequentialAwaitsWithDifferentExceptions() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+            import groovy.concurrent.Awaitable
+
+            async caller() {
+                def log = []
+
+                try {
+                    await Awaitable.failed(new java.io.IOException("io"))
+                } catch (java.io.IOException e) {
+                    log << "io:${e.message}"
+                }
+
+                try {
+                    await Awaitable.failed(new NumberFormatException("nfe"))
+                } catch (NumberFormatException e) {
+                    log << "nfe:${e.message}"
+                }
+
+                try {
+                    await Awaitable.failed(new java.sql.SQLException("sql"))
+                } catch (java.sql.SQLException e) {
+                    log << "sql:${e.message}"
+                }
+
+                return log.join(";")
+            }
+
+            assert await(caller()) == "io:io;nfe:nfe;sql:sql"
+        '''
+    }
+
+    // =========================================================================
+    // 20. Awaitable.failed + await throws original
+    // =========================================================================
+
+    @Test
+    void testAwaitOnFailedAwaitable() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+            import groovy.concurrent.Awaitable
+
+            async caller() {
+                try {
+                    await Awaitable.failed(new java.io.IOException("pre-failed"))
+                    assert false
+                } catch (java.io.IOException e) {
+                    return e.message
+                }
+            }
+
+            assert await(caller()) == "pre-failed"
+        '''
+    }
+}
diff --git a/src/test/groovy/org/codehaus/groovy/transform/AsyncFrameworkIntegrationTest.groovy b/src/test/groovy/org/codehaus/groovy/transform/AsyncFrameworkIntegrationTest.groovy
new file mode 100644
index 0000000000..9dd2824919
--- /dev/null
+++ b/src/test/groovy/org/codehaus/groovy/transform/AsyncFrameworkIntegrationTest.groovy
@@ -0,0 +1,506 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.codehaus.groovy.transform
+
+import groovy.concurrent.AsyncStream
+import groovy.concurrent.Awaitable
+import groovy.concurrent.AwaitableAdapter
+import groovy.concurrent.AwaitableAdapterRegistry
+import groovy.concurrent.GroovyPromise
+import static groovy.transform.AsyncUtils.*
+import io.reactivex.rxjava3.core.Flowable
+import io.reactivex.rxjava3.core.Maybe
+import io.reactivex.rxjava3.core.Observable
+import io.reactivex.rxjava3.core.Single
+import org.junit.jupiter.api.AfterEach
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import reactor.core.publisher.Flux
+import reactor.core.publisher.Mono
+
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.CompletionStage
+
+/**
+ * Integration tests for Groovy async/await with third-party reactive
+ * frameworks: Reactor (Spring WebFlux foundation), RxJava 3, and Spring-style
+ * async patterns.
+ * <p>
+ * These tests demonstrate that the {@link AwaitableAdapter} SPI allows
+ * transparent integration without coupling Groovy's core to any specific
+ * reactive library.
+ */
+class AsyncFrameworkIntegrationTest {
+
+    private ReactorAwaitableAdapter reactorAdapter
+    private RxJavaAwaitableAdapter rxJavaAdapter
+
+    @BeforeEach
+    void registerAdapters() {
+        reactorAdapter = new ReactorAwaitableAdapter()
+        rxJavaAdapter = new RxJavaAwaitableAdapter()
+        AwaitableAdapterRegistry.register(reactorAdapter)
+        AwaitableAdapterRegistry.register(rxJavaAdapter)
+    }
+
+    @AfterEach
+    void resetExecutor() {
+        setExecutor(null)
+    }
+
+    // =====================================================================
+    // Reactor (Spring WebFlux foundation) tests
+    // =====================================================================
+
+    @Test
+    void testAwaitReactorMono() {
+        def mono = Mono.just("reactor-value")
+        def result = await(mono)
+        assert result == "reactor-value"
+    }
+
+    @Test
+    void testAwaitReactorMonoWithMap() {
+        def mono = Mono.just(10).map { it * 3 }
+        def result = await(mono)
+        assert result == 30
+    }
+
+    @Test
+    void testAwaitReactorMonoEmpty() {
+        def mono = Mono.empty()
+        def result = await(mono)
+        assert result == null
+    }
+
+    @Test
+    void testAwaitReactorMonoError() {
+        def mono = Mono.error(new IllegalStateException("reactor error"))
+        try {
+            await(mono)
+            assert false : "should have thrown"
+        } catch (IllegalStateException e) {
+            assert e.message == "reactor error"
+        }
+    }
+
+    @Test
+    void testAwaitReactorMonoDeferred() {
+        def mono = Mono.fromSupplier { Thread.sleep(50); "deferred" }
+        def result = await(mono)
+        assert result == "deferred"
+    }
+
+    @Test
+    void testAwaitReactorMonoChain() {
+        def mono = Mono.just("hello")
+            .map { it.toUpperCase() }
+            .flatMap { s -> Mono.just("${s}!") }
+        def result = await(mono)
+        assert result == "HELLO!"
+    }
+
+    @Test
+    void testForAwaitReactorFlux() {
+        def flux = Flux.just(1, 2, 3, 4, 5)
+        AsyncStream<Integer> stream = toAsyncStream(flux)
+        def results = []
+        while (await(stream.moveNext())) {
+            results << stream.getCurrent()
+        }
+        assert results == [1, 2, 3, 4, 5]
+    }
+
+    @Test
+    void testForAwaitReactorFluxWithOperators() {
+        def flux = Flux.range(1, 10).filter { it % 2 == 0 }.map { it * 10 }
+        AsyncStream<Integer> stream = toAsyncStream(flux)
+        def results = []
+        while (await(stream.moveNext())) {
+            results << stream.getCurrent()
+        }
+        assert results == [20, 40, 60, 80, 100]
+    }
+
+    @Test
+    void testForAwaitReactorFluxEmpty() {
+        def flux = Flux.empty()
+        AsyncStream stream = toAsyncStream(flux)
+        def results = []
+        while (await(stream.moveNext())) {
+            results << stream.getCurrent()
+        }
+        assert results == []
+    }
+
+    @Test
+    void testReactorMonoToAwaitableApi() {
+        def mono = Mono.just(42)
+        Awaitable<Integer> awaitable = AwaitableAdapterRegistry.toAwaitable(mono)
+        assert awaitable.get() == 42
+        assert awaitable.isDone()
+    }
+
+    @Test
+    void testReactorMonoAwaitableThen() {
+        def mono = Mono.just(5)
+        Awaitable<Integer> a = AwaitableAdapterRegistry.toAwaitable(mono)
+        Awaitable<Integer> doubled = a.then { it * 2 }
+        assert doubled.get() == 10
+    }
+
+    // =====================================================================
+    // RxJava 3 tests
+    // =====================================================================
+
+    @Test
+    void testAwaitRxJavaSingle() {
+        def single = Single.just("rxjava-value")
+        def result = await(single)
+        assert result == "rxjava-value"
+    }
+
+    @Test
+    void testAwaitRxJavaSingleWithMap() {
+        def single = Single.just(7).map { it * 6 }
+        def result = await(single)
+        assert result == 42
+    }
+
+    @Test
+    void testAwaitRxJavaSingleError() {
+        def single = Single.error(new RuntimeException("rx error"))
+        try {
+            await(single)
+            assert false : "should have thrown"
+        } catch (RuntimeException e) {
+            assert e.message == "rx error"
+        }
+    }
+
+    @Test
+    void testAwaitRxJavaMaybe() {
+        def maybe = Maybe.just("maybe-value")
+        def result = await(maybe)
+        assert result == "maybe-value"
+    }
+
+    @Test
+    void testAwaitRxJavaMaybeEmpty() {
+        def maybe = Maybe.empty()
+        def result = await(maybe)
+        assert result == null
+    }
+
+    @Test
+    void testForAwaitRxJavaObservable() {
+        def observable = Observable.just("a", "b", "c")
+        AsyncStream<String> stream = toAsyncStream(observable)
+        def results = []
+        while (await(stream.moveNext())) {
+            results << stream.getCurrent()
+        }
+        assert results == ["a", "b", "c"]
+    }
+
+    @Test
+    void testForAwaitRxJavaObservableWithOperators() {
+        def observable = Observable.range(1, 5).filter { it > 2 }.map { "item-${it}" }
+        AsyncStream<String> stream = toAsyncStream(observable)
+        def results = []
+        while (await(stream.moveNext())) {
+            results << stream.getCurrent()
+        }
+        assert results == ["item-3", "item-4", "item-5"]
+    }
+
+    @Test
+    void testForAwaitRxJavaFlowable() {
+        def flowable = Flowable.just(10, 20, 30)
+        AsyncStream<Integer> stream = toAsyncStream(flowable)
+        def results = []
+        while (await(stream.moveNext())) {
+            results << stream.getCurrent()
+        }
+        assert results == [10, 20, 30]
+    }
+
+    @Test
+    void testRxJavaSingleToAwaitableApi() {
+        def single = Single.just("adapted")
+        Awaitable<String> awaitable = AwaitableAdapterRegistry.toAwaitable(single)
+        assert awaitable.get() == "adapted"
+        assert awaitable.isDone()
+    }
+
+    // =====================================================================
+    // Spring-style async pattern tests
+    //
+    // Spring @Async methods return CompletableFuture (Spring 6+) or
+    // CompletionStage. Spring WebFlux returns Reactor Mono/Flux.
+    // These tests demonstrate seamless interoperability.
+    // =====================================================================
+
+    @Test
+    void testSpringStyleCompletableFutureService() {
+        // Simulates a Spring @Async service method returning CompletableFuture
+        def springService = new SpringStyleService()
+        def result = await(springService.fetchUser(1L))
+        assert result == [id: 1L, name: "User1"]
+    }
+
+    @Test
+    void testSpringStyleCompletionStageService() {
+        // Simulates a Spring service returning CompletionStage
+        def springService = new SpringStyleService()
+        CompletionStage<String> stage = springService.processAsync("hello")
+        def result = await(stage)
+        assert result == "HELLO"
+    }
+
+    @Test
+    void testSpringWebFluxMonoEndpoint() {
+        // Simulates a Spring WebFlux reactive endpoint returning Mono
+        def controller = new SpringWebFluxStyleController()
+        def mono = controller.getUser(42L)
+        def result = await(mono)
+        assert result == [id: 42L, name: "User42"]
+    }
+
+    @Test
+    void testSpringWebFluxFluxEndpoint() {
+        // Simulates a Spring WebFlux reactive endpoint returning Flux
+        def controller = new SpringWebFluxStyleController()
+        def flux = controller.listUsers()
+        AsyncStream stream = toAsyncStream(flux)
+        def results = []
+        while (await(stream.moveNext())) {
+            results << stream.getCurrent()
+        }
+        assert results.size() == 3
+        assert results[0].name == "User1"
+        assert results[2].name == "User3"
+    }
+
+    @Test
+    void testSpringStyleAsyncChainWithReactorMono() {
+        // Simulates chaining Spring async calls with Reactor
+        def userService = new SpringWebFluxStyleController()
+        def enriched = userService.getUser(1L)
+            .map { user -> user + [role: "admin"] }
+        def result = await(enriched)
+        assert result.name == "User1"
+        assert result.role == "admin"
+    }
+
+    @Test
+    void testSpringStyleMultipleAsyncCalls() {
+        // Simulates parallel Spring async calls gathered with Groovy await
+        def service = new SpringStyleService()
+        def f1 = service.fetchUser(1L)
+        def f2 = service.fetchUser(2L)
+        def f3 = service.fetchUser(3L)
+        def results = [await(f1), await(f2), await(f3)]
+        assert results.collect { it.name } == ["User1", "User2", "User3"]
+    }
+
+    @Test
+    void testSpringStyleCompletionStageAdapter() {
+        // CompletionStage (Spring's return type) directly supported by built-in adapter
+        CompletionStage<String> stage = CompletableFuture.supplyAsync { "stage-value" }
+        Awaitable<String> awaitable = AwaitableAdapterRegistry.toAwaitable(stage)
+        assert awaitable.get() == "stage-value"
+    }
+
+    // =====================================================================
+    // Cross-framework interoperability
+    // =====================================================================
+
+    @Test
+    void testMixedFrameworkAwait() {
+        // Await values from different frameworks in the same context
+        def mono = Mono.just(10)
+        def single = Single.just(20)
+        def cf = CompletableFuture.supplyAsync { 30 }
+
+        def r1 = await(mono)
+        def r2 = await(single)
+        def r3 = await(cf)
+        assert r1 + r2 + r3 == 60
+    }
+
+    @Test
+    void testReactorToRxJavaInterop() {
+        // Convert between frameworks via Awaitable abstraction
+        def mono = Mono.just("from-reactor")
+        Awaitable<String> awaitable = AwaitableAdapterRegistry.toAwaitable(mono)
+
+        // Use Awaitable's CompletableFuture interop to bridge to any framework
+        CompletableFuture<String> cf = awaitable.toCompletableFuture()
+        assert cf.get() == "from-reactor"
+    }
+
+    @Test
+    void testAwaitableExceptionallyWithReactor() {
+        def mono = Mono.error(new RuntimeException("fail"))
+        Awaitable<String> awaitable = AwaitableAdapterRegistry.toAwaitable(mono)
+        Awaitable<String> recovered = awaitable.exceptionally { "recovered" }
+        assert recovered.get() == "recovered"
+    }
+
+    @Test
+    void testAwaitableThenComposeAcrossFrameworks() {
+        def mono = Mono.just(5)
+        Awaitable<Integer> a = AwaitableAdapterRegistry.toAwaitable(mono)
+        Awaitable<Integer> composed = a.thenCompose { val ->
+            // Chain into another Awaitable from a different source
+            GroovyPromise.of(CompletableFuture.supplyAsync { val * 10 })
+        }
+        assert composed.get() == 50
+    }
+
+    // =====================================================================
+    // Adapter implementation classes
+    // =====================================================================
+
+    /**
+     * Reactor adapter: supports {@link Mono} as {@link Awaitable} and
+     * {@link Flux} as {@link AsyncStream}. This is the pattern Spring
+     * WebFlux users would register.
+     */
+    static class ReactorAwaitableAdapter implements AwaitableAdapter {
+
+        @Override
+        boolean supportsAwaitable(Class<?> type) {
+            return Mono.isAssignableFrom(type)
+        }
+
+        @Override
+        Awaitable toAwaitable(Object source) {
+            return new GroovyPromise<>(((Mono) source).toFuture())
+        }
+
+        @Override
+        boolean supportsAsyncStream(Class<?> type) {
+            return Flux.isAssignableFrom(type)
+        }
+
+        @Override
+        AsyncStream toAsyncStream(Object source) {
+            def iter = ((Flux) source).toIterable().iterator()
+            return new AsyncStream() {
+                private Object current
+
+                @Override
+                Awaitable<Boolean> moveNext() {
+                    boolean hasNext = iter.hasNext()
+                    if (hasNext) current = iter.next()
+                    return Awaitable.of(hasNext)
+                }
+
+                @Override
+                Object getCurrent() { return current }
+            }
+        }
+    }
+
+    /**
+     * RxJava 3 adapter: supports {@link Single}/{@link Maybe} as
+     * {@link Awaitable} and {@link Observable}/{@link Flowable} as
+     * {@link AsyncStream}.
+     */
+    static class RxJavaAwaitableAdapter implements AwaitableAdapter {
+
+        @Override
+        boolean supportsAwaitable(Class<?> type) {
+            return Single.isAssignableFrom(type) || Maybe.isAssignableFrom(type)
+        }
+
+        @Override
+        Awaitable toAwaitable(Object source) {
+            if (source instanceof Single) {
+                return new GroovyPromise<>(((Single) source).toCompletionStage().toCompletableFuture())
+            }
+            if (source instanceof Maybe) {
+                return new GroovyPromise<>(((Maybe) source).toCompletionStage(null).toCompletableFuture())
+            }
+            throw new IllegalArgumentException("Unsupported RxJava type: ${source.class}")
+        }
+
+        @Override
+        boolean supportsAsyncStream(Class<?> type) {
+            return Observable.isAssignableFrom(type) || Flowable.isAssignableFrom(type)
+        }
+
+        @Override
+        AsyncStream toAsyncStream(Object source) {
+            Iterator iter
+            if (source instanceof Observable) {
+                iter = ((Observable) source).blockingIterable().iterator()
+            } else if (source instanceof Flowable) {
+                iter = ((Flowable) source).blockingIterable().iterator()
+            } else {
+                throw new IllegalArgumentException("Unsupported RxJava type: ${source.class}")
+            }
+            return new AsyncStream() {
+                private Object current
+
+                @Override
+                Awaitable<Boolean> moveNext() {
+                    boolean hasNext = iter.hasNext()
+                    if (hasNext) current = iter.next()
+                    return Awaitable.of(hasNext)
+                }
+
+                @Override
+                Object getCurrent() { return current }
+            }
+        }
+    }
+
+    // =====================================================================
+    // Spring-style service stubs (simulate Spring @Async / WebFlux)
+    // =====================================================================
+
+    /** Simulates a Spring {@code @Async} service returning CompletableFuture. */
+    static class SpringStyleService {
+        CompletableFuture<Map> fetchUser(long id) {
+            CompletableFuture.supplyAsync { [id: id, name: "User${id}"] }
+        }
+
+        CompletionStage<String> processAsync(String input) {
+            CompletableFuture.supplyAsync { input.toUpperCase() }
+        }
+    }
+
+    /** Simulates a Spring WebFlux controller returning Reactor types. */
+    static class SpringWebFluxStyleController {
+        Mono<Map> getUser(long id) {
+            Mono.just([id: id, name: "User${id}"])
+        }
+
+        Flux<Map> listUsers() {
+            Flux.just(
+                [id: 1L, name: "User1"],
+                [id: 2L, name: "User2"],
+                [id: 3L, name: "User3"]
+            )
+        }
+    }
+}
diff --git a/src/test/groovy/org/codehaus/groovy/transform/AsyncTransformTest.groovy b/src/test/groovy/org/codehaus/groovy/transform/AsyncTransformTest.groovy
new file mode 100644
index 0000000000..1e2ea547d6
--- /dev/null
+++ b/src/test/groovy/org/codehaus/groovy/transform/AsyncTransformTest.groovy
@@ -0,0 +1,1401 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.codehaus.groovy.transform
+
+import groovy.transform.Async
+import static groovy.transform.AsyncUtils.*
+import org.codehaus.groovy.control.CompilationFailedException
+import org.junit.jupiter.api.AfterEach
+import org.junit.jupiter.api.Test
+
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.ConcurrentHashMap
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.ExecutorService
+import java.util.concurrent.Executors
+import java.util.concurrent.ForkJoinPool
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.atomic.AtomicInteger
+
+import static groovy.test.GroovyAssert.assertScript
+import static groovy.test.GroovyAssert.shouldFail
+
+/**
+ * Tests for async/await: @Async annotation, async/await keywords,
+ * Awaitable abstraction, for-await, and AsyncUtils.
+ */
+class AsyncTransformTest {
+
+    @AfterEach
+    void resetExecutor() {
+        setExecutor(null)
+    }
+
+    // ==== @Async annotation tests ====
+
+    @Test
+    void testBasicAsyncReturnsAwaitable() {
+        assertScript '''
+            import groovy.transform.Async
+            import groovy.concurrent.Awaitable
+
+            class Service {
+                @Async
+                def fetchData() {
+                    return "hello"
+                }
+            }
+
+            def service = new Service()
+            def future = service.fetchData()
+            assert future instanceof Awaitable
+            assert future.get() == "hello"
+        '''
+    }
+
+    @Test
+    void testAsyncWithTypedReturnValue() {
+        assertScript '''
+            import groovy.transform.Async
+            import groovy.concurrent.Awaitable
+
+            class Service {
+                @Async
+                String compute() {
+                    return "typed-result"
+                }
+            }
+
+            def service = new Service()
+            Awaitable<String> future = service.compute()
+            assert future.get() == "typed-result"
+        '''
+    }
+
+    @Test
+    void testAsyncWithIntReturnValue() {
+        assertScript '''
+            import groovy.transform.Async
+            import groovy.concurrent.Awaitable
+
+            class Calculator {
+                @Async
+                int add(int a, int b) {
+                    return a + b
+                }
+            }
+
+            def calc = new Calculator()
+            def future = calc.add(3, 4)
+            assert future instanceof Awaitable
+            assert future.get() == 7
+        '''
+    }
+
+    @Test
+    void testAsyncVoidMethod() {
+        assertScript '''
+            import groovy.transform.Async
+            import groovy.concurrent.Awaitable
+            import java.util.concurrent.atomic.AtomicBoolean
+
+            class Worker {
+                AtomicBoolean executed = new AtomicBoolean(false)
+
+                @Async
+                void doWork() {
+                    executed.set(true)
+                }
+            }
+
+            def worker = new Worker()
+            def future = worker.doWork()
+            assert future instanceof Awaitable
+            future.get()
+            assert worker.executed.get()
+        '''
+    }
+
+    @Test
+    void testAsyncStaticMethod() {
+        assertScript '''
+            import groovy.transform.Async
+            import groovy.concurrent.Awaitable
+
+            class Util {
+                @Async
+                static String process(String input) {
+                    return input.toUpperCase()
+                }
+            }
+
+            def future = Util.process("hello")
+            assert future instanceof Awaitable
+            assert future.get() == "HELLO"
+        '''
+    }
+
+    // ==== await() call-style tests (backward compat) ====
+
+    @Test
+    void testAwaitCallInAsyncMethod() {
+        assertScript '''
+            import groovy.transform.Async
+            import java.util.concurrent.CompletableFuture
+
+            class Service {
+                @Async
+                def fetchAndProcess() {
+                    def value = await(CompletableFuture.supplyAsync { 42 })
+                    return value * 2
+                }
+            }
+
+            def service = new Service()
+            assert service.fetchAndProcess().get() == 84
+        '''
+    }
+
+    @Test
+    void testMultipleSequentialAwaits() {
+        assertScript '''
+            import groovy.transform.Async
+            import java.util.concurrent.CompletableFuture
+
+            class Service {
+                @Async
+                def combineResults() {
+                    def a = await(CompletableFuture.supplyAsync { 10 })
+                    def b = await(CompletableFuture.supplyAsync { 20 })
+                    def c = await(CompletableFuture.supplyAsync { 30 })
+                    return a + b + c
+                }
+            }
+
+            def service = new Service()
+            assert service.combineResults().get() == 60
+        '''
+    }
+
+    @Test
+    void testAwaitWithDependentFutures() {
+        assertScript '''
+            import groovy.transform.Async
+            import java.util.concurrent.CompletableFuture
+
+            class Service {
+                @Async
+                def pipeline() {
+                    def raw = await(CompletableFuture.supplyAsync { "hello world" })
+                    def upper = await(CompletableFuture.supplyAsync { raw.toUpperCase() })
+                    return upper
+                }
+            }
+
+            def service = new Service()
+            assert service.pipeline().get() == "HELLO WORLD"
+        '''
+    }
+
+    @Test
+    void testNestedAwaitInExpression() {
+        assertScript '''
+            import groovy.transform.Async
+            import java.util.concurrent.CompletableFuture
+
+            class Service {
+                @Async
+                def nestedExpr() {
+                    return await(CompletableFuture.supplyAsync { 5 }) + await(CompletableFuture.supplyAsync { 3 })
+                }
+            }
+
+            def service = new Service()
+            assert service.nestedExpr().get() == 8
+        '''
+    }
+
+    @Test
+    void testAwaitInsideConditional() {
+        assertScript '''
+            import groovy.transform.Async
+            import java.util.concurrent.CompletableFuture
+
+            class Service {
+                @Async
+                def conditional(boolean flag) {
+                    if (flag) {
+                        return await(CompletableFuture.supplyAsync { "yes" })
+                    } else {
+                        return await(CompletableFuture.supplyAsync { "no" })
+                    }
+                }
+            }
+
+            def service = new Service()
+            assert service.conditional(true).get() == "yes"
+            assert service.conditional(false).get() == "no"
+        '''
+    }
+
+    @Test
+    void testAwaitInsideLoop() {
+        assertScript '''
+            import groovy.transform.Async
+            import java.util.concurrent.CompletableFuture
+
+            class Service {
+                @Async
+                def sumAsync(int count) {
+                    int sum = 0
+                    for (int i = 1; i <= count; i++) {
+                        sum += await(CompletableFuture.supplyAsync { i })
+                    }
+                    return sum
+                }
+            }
+
+            def service = new Service()
+            assert service.sumAsync(5).get() == 15
+        '''
+    }
+
+    @Test
+    void testAwaitInsideTryCatch() {
+        assertScript '''
+            import groovy.transform.Async
+            import java.util.concurrent.CompletableFuture
+
+            class Service {
+                @Async
+                def safeOperation() {
+                    try {
+                        def result = await(CompletableFuture.supplyAsync { 42 })
+                        return result
+                    } catch (Exception e) {
+                        return -1
+                    }
+                }
+            }
+
+            def service = new Service()
+            assert service.safeOperation().get() == 42
+        '''
+    }
+
+    @Test
+    void testAwaitInsideClosureInAsyncMethod() {
+        assertScript '''
+            import groovy.transform.Async
+            import java.util.concurrent.CompletableFuture
+
+            class Service {
+                @Async
+                def collectResults() {
+                    def futures = [
+                        CompletableFuture.supplyAsync { 1 },
+                        CompletableFuture.supplyAsync { 2 },
+                        CompletableFuture.supplyAsync { 3 }
+                    ]
+                    return futures.collect { f -> await(f) }
+                }
+            }
+
+            def service = new Service()
+            assert service.collectResults().get() == [1, 2, 3]
+        '''
+    }
+
+    // ==== Exception handling ====
+
+    @Test
+    void testAsyncExceptionPropagation() {
+        assertScript '''
+            import groovy.transform.Async
+            import java.util.concurrent.ExecutionException
+
+            class Service {
+                @Async
+                def failingMethod() {
+                    throw new IllegalStateException("async failure")
+                }
+            }
+
+            def service = new Service()
+            def future = service.failingMethod()
+            try {
+                future.get()
+                assert false : "should have thrown"
+            } catch (ExecutionException e) {
+                assert e.cause instanceof IllegalStateException
+                assert e.cause.message == "async failure"
+            }
+        '''
+    }
+
+    @Test
+    void testAwaitExceptionFromFuture() {
+        assertScript '''
+            import groovy.transform.Async
+            import java.util.concurrent.CompletableFuture
+            import java.util.concurrent.ExecutionException
+
+            class Service {
+                @Async
+                def awaitFailing() {
+                    return await(CompletableFuture.supplyAsync {
+                        throw new IllegalArgumentException("inner failure")
+                    })
+                }
+            }
+
+            def service = new Service()
+            try {
+                service.awaitFailing().get()
+                assert false : "should have thrown"
+            } catch (ExecutionException e) {
+                assert e.cause instanceof IllegalArgumentException
+                assert e.cause.message == "inner failure"
+            }
+        '''
+    }
+
+    @Test
+    void testAwaitExceptionCaughtInTryCatch() {
+        assertScript '''
+            import groovy.transform.Async
+            import java.util.concurrent.CompletableFuture
+
+            class Service {
+                @Async
+                def recoverFromError() {
+                    try {
+                        return await(CompletableFuture.supplyAsync {
+                            throw new IllegalArgumentException("oops")
+                        })
+                    } catch (IllegalArgumentException e) {
+                        return "recovered: " + e.message
+                    }
+                }
+            }
+
+            def service = new Service()
+            assert service.recoverFromError().get() == "recovered: oops"
+        '''
+    }
+
+    // ==== Custom executor ====
+
+    @Test
+    void testAsyncWithCustomExecutor() {
+        assertScript '''
+            import groovy.transform.Async
+            import java.util.concurrent.Executors
+
+            class Service {
+                def myPool = Executors.newSingleThreadExecutor()
+
+                @Async(executor = "myPool")
+                def runOnCustomPool() {
+                    return Thread.currentThread().name
+                }
+            }
+
+            def service = new Service()
+            def threadName = service.runOnCustomPool().get()
+            assert threadName.contains("pool")
+            service.myPool.shutdown()
+        '''
+    }
+
+    @Test
+    void testAsyncStaticMethodWithStaticExecutor() {
+        assertScript '''
+            import groovy.transform.Async
+            import java.util.concurrent.Executors
+
+            class Service {
+                static def pool = Executors.newSingleThreadExecutor()
+
+                @Async(executor = "pool")
+                static def runStatic() {
+                    return "static-result"
+                }
+            }
+
+            assert Service.runStatic().get() == "static-result"
+            Service.pool.shutdown()
+        '''
+    }
+
+    // ==== Concurrency ====
+
+    @Test
+    void testAsyncMethodRunsOnDifferentThread() {
+        assertScript '''
+            import groovy.transform.Async
+
+            class Service {
+                @Async
+                def getThreadName() {
+                    return Thread.currentThread().name
+                }
+            }
+
+            def service = new Service()
+            def callerThread = Thread.currentThread().name
+            def asyncThread = service.getThreadName().get()
+            assert asyncThread != callerThread || asyncThread.contains("ForkJoinPool")
+        '''
+    }
+
+    @Test
+    void testConcurrentAsyncExecution() {
+        assertScript '''
+            import groovy.transform.Async
+            import java.util.concurrent.CountDownLatch
+            import java.util.concurrent.atomic.AtomicInteger
+
+            class Service {
+                AtomicInteger concurrentCount = new AtomicInteger(0)
+                AtomicInteger maxConcurrent = new AtomicInteger(0)
+
+                @Async
+                def concurrentTask(CountDownLatch startLatch) {
+                    int current = concurrentCount.incrementAndGet()
+                    maxConcurrent.updateAndGet { max -> Math.max(max, current) }
+                    startLatch.await(5, java.util.concurrent.TimeUnit.SECONDS)
+                    concurrentCount.decrementAndGet()
+                    return current
+                }
+            }
+
+            def service = new Service()
+            def latch = new CountDownLatch(1)
+            def futures = (1..10).collect { service.concurrentTask(latch) }
+            Thread.sleep(500)
+            latch.countDown()
+            futures.each { it.get(5, java.util.concurrent.TimeUnit.SECONDS) }
+            assert service.maxConcurrent.get() > 1
+        '''
+    }
+
+    @Test
+    void testHighConcurrencyStressTest() {
+        assertScript '''
+            import groovy.transform.Async
+            import java.util.concurrent.atomic.AtomicInteger
+
+            class Service {
+                AtomicInteger counter = new AtomicInteger(0)
+
+                @Async
+                def increment() {
+                    counter.incrementAndGet()
+                    return counter.get()
+                }
+            }
+
+            def service = new Service()
+            int taskCount = 1000
+            def futures = (1..taskCount).collect { service.increment() }
+            futures.each { it.get(10, java.util.concurrent.TimeUnit.SECONDS) }
+            assert service.counter.get() == taskCount
+        '''
+    }
+
+    @Test
+    void testParallelAwaitPattern() {
+        assertScript '''
+            import groovy.transform.Async
+            import java.util.concurrent.CompletableFuture
+
+            class Service {
+                CompletableFuture<Integer> asyncCompute(int value) {
+                    return CompletableFuture.supplyAsync {
+                        Thread.sleep(50)
+                        return value * 2
+                    }
+                }
+
+                @Async
+                def parallelFetch() {
+                    def f1 = asyncCompute(1)
+                    def f2 = asyncCompute(2)
+                    def f3 = asyncCompute(3)
+                    return await(f1) + await(f2) + await(f3)
+                }
+            }
+
+            def service = new Service()
+            long start = System.currentTimeMillis()
+            def result = service.parallelFetch().get()
+            long elapsed = System.currentTimeMillis() - start
+            assert result == 12
+            assert elapsed < 500
+        '''
+    }
+
+    // ==== AsyncUtils standalone ====
+
+    @Test
+    void testAsyncUtilsAsyncClosure() {
+        def future = async { 42 }
+        assert future instanceof groovy.concurrent.Awaitable
+        assert future.get() == 42
+    }
+
+    @Test
+    void testAsyncUtilsAwait() {
+        def future = CompletableFuture.supplyAsync { "hello" }
+        def result = await(future)
+        assert result == "hello"
+    }
+
+    @Test
+    void testAsyncUtilsAwaitAll() {
+        def f1 = CompletableFuture.supplyAsync { 1 }
+        def f2 = CompletableFuture.supplyAsync { 2 }
+        def f3 = CompletableFuture.supplyAsync { 3 }
+        def results = awaitAll(f1, f2, f3)
+        assert results == [1, 2, 3]
+    }
+
+    @Test
+    void testAsyncUtilsAwaitAny() {
+        def f1 = CompletableFuture.supplyAsync { "first" }
+        def f2 = new CompletableFuture()
+        def f3 = new CompletableFuture()
+        def result = awaitAny(f1, f2, f3)
+        assert result == "first"
+    }
+
+    @Test
+    void testAsyncUtilsSetCustomExecutor() {
+        def pool = Executors.newSingleThreadExecutor()
+        try {
+            setExecutor(pool)
+            def future = async { Thread.currentThread().name }
+            assert future.get().contains("pool")
+        } finally {
+            setExecutor(null)
+            pool.shutdown()
+        }
+    }
+
+    @Test
+    void testAsyncUtilsAwaitFutureInterface() {
+        def pool = Executors.newSingleThreadExecutor()
+        try {
+            java.util.concurrent.Future<String> future = pool.submit({ "from-executor" } as java.util.concurrent.Callable)
+            def result = await(future)
+            assert result == "from-executor"
+        } finally {
+            pool.shutdown()
+        }
+    }
+
+    @Test
+    void testAsyncUtilsAwaitExceptionUnwrapping() {
+        def future = CompletableFuture.supplyAsync {
+            throw new IllegalStateException("test error")
+        }
+        try {
+            await(future)
+            assert false : "should have thrown"
+        } catch (IllegalStateException e) {
+            assert e.message == "test error"
+        }
+    }
+
+    // ==== Compilation errors ====
+
+    @Test
+    void testAsyncOnAbstractMethodFails() {
+        def err = shouldFail CompilationFailedException, '''
+            import groovy.transform.Async
+
+            abstract class Service {
+                @Async
+                abstract def fetchData()
+            }
+        '''
+        assert err.message.contains("cannot be applied to abstract method")
+    }
+
+    @Test
+    void testAsyncOnAwaitableReturnTypeFails() {
+        def err = shouldFail CompilationFailedException, '''
+            import groovy.transform.Async
+            import groovy.concurrent.Awaitable
+
+            class Service {
+                @Async
+                Awaitable<String> fetchData() {
+                    return groovy.concurrent.Awaitable.of("hello")
+                }
+            }
+        '''
+        assert err.message.contains("already returns an async type")
+    }
+
+    @Test
+    void testAsyncWithMissingExecutorFieldFails() {
+        def err = shouldFail CompilationFailedException, '''
+            import groovy.transform.Async
+
+            class Service {
+                @Async(executor = "nonExistentField")
+                def fetchData() {
+                    return "hello"
+                }
+            }
+        '''
+        assert err.message.contains("executor field 'nonExistentField' not found")
+    }
+
+    @Test
+    void testAsyncStaticMethodWithInstanceExecutorFails() {
+        def err = shouldFail CompilationFailedException, '''
+            import groovy.transform.Async
+            import java.util.concurrent.Executors
+
+            class Service {
+                def myPool = Executors.newSingleThreadExecutor()
+
+                @Async(executor = "myPool")
+                static def fetchData() {
+                    return "hello"
+                }
+            }
+        '''
+        assert err.message.contains("must be static for static method")
+    }
+
+    // ==== Static import async/await ====
+
+    @Test
+    void testAsyncClosureWithStaticImport() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+            import java.util.concurrent.CompletableFuture
+
+            def future = async {
+                def a = await(CompletableFuture.supplyAsync { 10 })
+                def b = await(CompletableFuture.supplyAsync { 20 })
+                return a + b
+            }
+            assert future.get() == 30
+        '''
+    }
+
+    @Test
+    void testAsyncClosureExceptionHandling() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+            import java.util.concurrent.ExecutionException
+
+            def future = async {
+                throw new IllegalStateException("closure error")
+            }
+            try {
+                future.get()
+                assert false : "should have thrown"
+            } catch (ExecutionException e) {
+                assert e.cause instanceof IllegalStateException
+                assert e.cause.message == "closure error"
+            }
+        '''
+    }
+
+    // ==== Integration scenarios ====
+
+    @Test
+    void testAsyncProducerConsumerPattern() {
+        assertScript '''
+            import groovy.transform.Async
+            import groovy.concurrent.Awaitable
+
+            class Producer {
+                @Async
+                def produce(int id) {
+                    Thread.sleep(10)
+                    return "item-${id}"
+                }
+            }
+
+            class Consumer {
+                @Async
+                def consume(Awaitable<String> itemFuture) {
+                    def item = await(itemFuture)
+                    return "consumed: ${item}"
+                }
+            }
+
+            def producer = new Producer()
+            def consumer = new Consumer()
+            def item = producer.produce(1)
+            def result = consumer.consume(item)
+            assert result.get() == "consumed: item-1"
+        '''
+    }
+
+    @Test
+    void testAsyncChainedCalls() {
+        assertScript '''
+            import groovy.transform.Async
+            import groovy.concurrent.Awaitable
+
+            class Pipeline {
+                @Async
+                def step1() { return 1 }
+
+                @Async
+                def step2(Awaitable<Integer> input) {
+                    return await(input) + 10
+                }
+
+                @Async
+                def step3(Awaitable<Integer> input) {
+                    return await(input) * 2
+                }
+            }
+
+            def p = new Pipeline()
+            def r1 = p.step1()
+            def r2 = p.step2(r1)
+            def r3 = p.step3(r2)
+            assert r3.get() == 22
+        '''
+    }
+
+    @Test
+    void testAsyncWithMethodParameters() {
+        assertScript '''
+            import groovy.transform.Async
+            import java.util.concurrent.CompletableFuture
+
+            class Service {
+                @Async
+                def greet(String name, int times) {
+                    def parts = []
+                    for (int i = 0; i < times; i++) {
+                        parts << await(CompletableFuture.supplyAsync { "Hello ${name}" })
+                    }
+                    return parts.join(", ")
+                }
+            }
+
+            def service = new Service()
+            assert service.greet("World", 3).get() == "Hello World, Hello World, Hello World"
+        '''
+    }
+
+    @Test
+    void testAsyncWithCollectionProcessing() {
+        assertScript '''
+            import groovy.transform.Async
+            import java.util.concurrent.CompletableFuture
+
+            class BatchProcessor {
+                @Async
+                def processBatch(List<Integer> items) {
+                    def futures = items.collect { item ->
+                        CompletableFuture.supplyAsync { item * item }
+                    }
+                    return futures.collect { f -> await(f) }
+                }
+            }
+
+            def processor = new BatchProcessor()
+            assert processor.processBatch([1, 2, 3, 4, 5]).get() == [1, 4, 9, 16, 25]
+        '''
+    }
+
+    @Test
+    void testAsyncAwaitAllPattern() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+            import java.util.concurrent.CompletableFuture
+
+            class Service {
+                @Async
+                def fetchAll() {
+                    def f1 = CompletableFuture.supplyAsync { "a" }
+                    def f2 = CompletableFuture.supplyAsync { "b" }
+                    def f3 = CompletableFuture.supplyAsync { "c" }
+                    return awaitAll(f1, f2, f3)
+                }
+            }
+
+            def service = new Service()
+            assert service.fetchAll().get() == ["a", "b", "c"]
+        '''
+    }
+
+    @Test
+    void testAsyncInScript() {
+        assertScript '''
+            import groovy.transform.Async
+            import groovy.concurrent.Awaitable
+
+            @Async
+            def asyncAdd(int a, int b) {
+                return a + b
+            }
+
+            def future = asyncAdd(3, 4)
+            assert future instanceof Awaitable
+            assert future.get() == 7
+        '''
+    }
+
+    // ==== 'async' keyword modifier ====
+
+    @Test
+    void testAsyncKeywordModifier() {
+        assertScript '''
+            import groovy.concurrent.Awaitable
+
+            class Service {
+                async def fetchData() {
+                    return "from-async-keyword"
+                }
+            }
+
+            def service = new Service()
+            def result = service.fetchData()
+            assert result instanceof Awaitable
+            assert result.get() == "from-async-keyword"
+        '''
+    }
+
+    @Test
+    void testAsyncKeywordWithAwaitKeyword() {
+        assertScript '''
+            import groovy.concurrent.Awaitable
+            import java.util.concurrent.CompletableFuture
+
+            class Service {
+                async def compute() {
+                    def a = await CompletableFuture.supplyAsync { 10 }
+                    def b = await CompletableFuture.supplyAsync { 20 }
+                    return a + b
+                }
+            }
+
+            def service = new Service()
+            assert service.compute().get() == 30
+        '''
+    }
+
+    @Test
+    void testAsyncKeywordStaticMethod() {
+        assertScript '''
+            import groovy.concurrent.Awaitable
+
+            class Util {
+                async static def process(String input) {
+                    return input.toUpperCase()
+                }
+            }
+
+            def result = Util.process("hello")
+            assert result instanceof Awaitable
+            assert result.get() == "HELLO"
+        '''
+    }
+
+    @Test
+    void testAsyncKeywordVoidMethod() {
+        assertScript '''
+            import groovy.concurrent.Awaitable
+            import java.util.concurrent.atomic.AtomicBoolean
+
+            class Worker {
+                AtomicBoolean done = new AtomicBoolean(false)
+
+                async void doWork() {
+                    done.set(true)
+                }
+            }
+
+            def worker = new Worker()
+            def result = worker.doWork()
+            assert result instanceof Awaitable
+            result.get()
+            assert worker.done.get()
+        '''
+    }
+
+    @Test
+    void testAsyncKeywordInScript() {
+        assertScript '''
+            import groovy.concurrent.Awaitable
+
+            async def add(int a, int b) {
+                return a + b
+            }
+
+            def result = add(5, 7)
+            assert result instanceof Awaitable
+            assert result.get() == 12
+        '''
+    }
+
+    // ==== 'await' keyword expression ====
+
+    @Test
+    void testAwaitKeywordExpression() {
+        assertScript '''
+            import groovy.concurrent.Awaitable
+            import java.util.concurrent.CompletableFuture
+
+            class Service {
+                async def fetchAndDouble() {
+                    def value = await CompletableFuture.supplyAsync { 21 }
+                    return value * 2
+                }
+            }
+
+            def service = new Service()
+            assert service.fetchAndDouble().get() == 42
+        '''
+    }
+
+    @Test
+    void testAwaitKeywordPrecedence() {
+        assertScript '''
+            import groovy.concurrent.Awaitable
+            import java.util.concurrent.CompletableFuture
+
+            class Service {
+                async def addAwaits() {
+                    // 'await' should bind to the immediate expression, not to the sum
+                    // await a + await b  =>  (await a) + (await b)
+                    return (await CompletableFuture.supplyAsync { 10 }) + (await CompletableFuture.supplyAsync { 5 })
+                }
+            }
+
+            def service = new Service()
+            assert service.addAwaits().get() == 15
+        '''
+    }
+
+    @Test
+    void testAwaitKeywordWithParentheses() {
+        assertScript '''
+            import java.util.concurrent.CompletableFuture
+
+            class Service {
+                @groovy.transform.Async
+                def compute() {
+                    // await(expr) still works as await keyword + parenthesized expression
+                    def a = await(CompletableFuture.supplyAsync { 100 })
+                    return a
+                }
+            }
+
+            def service = new Service()
+            assert service.compute().get() == 100
+        '''
+    }
+
+    @Test
+    void testAwaitKeywordWithMethodCall() {
+        assertScript '''
+            import groovy.concurrent.Awaitable
+            import java.util.concurrent.CompletableFuture
+
+            class Service {
+                CompletableFuture<String> fetchRemote() {
+                    CompletableFuture.supplyAsync { "remote-data" }
+                }
+
+                async def process() {
+                    def data = await fetchRemote()
+                    return "processed: ${data}"
+                }
+            }
+
+            def service = new Service()
+            assert service.process().get() == "processed: remote-data"
+        '''
+    }
+
+    @Test
+    void testAwaitKeywordInConditional() {
+        assertScript '''
+            import groovy.concurrent.Awaitable
+            import java.util.concurrent.CompletableFuture
+
+            class Service {
+                async def check(boolean flag) {
+                    if (flag) {
+                        return await CompletableFuture.supplyAsync { "yes" }
+                    } else {
+                        return await CompletableFuture.supplyAsync { "no" }
+                    }
+                }
+            }
+
+            def service = new Service()
+            assert service.check(true).get() == "yes"
+            assert service.check(false).get() == "no"
+        '''
+    }
+
+    // ==== Awaitable abstraction tests ====
+
+    @Test
+    void testAwaitableInterface() {
+        assertScript '''
+            import groovy.concurrent.Awaitable
+            import groovy.concurrent.GroovyPromise
+            import java.util.concurrent.CompletableFuture
+
+            // Test Awaitable.of()
+            def a = Awaitable.of("hello")
+            assert a.get() == "hello"
+            assert a.isDone()
+
+            // Test Awaitable.failed()
+            def f = Awaitable.failed(new RuntimeException("oops"))
+            try {
+                f.get()
+                assert false
+            } catch (java.util.concurrent.ExecutionException e) {
+                assert e.cause.message == "oops"
+            }
+        '''
+    }
+
+    @Test
+    void testGroovyPromiseThen() {
+        assertScript '''
+            import groovy.concurrent.Awaitable
+            import groovy.concurrent.GroovyPromise
+            import java.util.concurrent.CompletableFuture
+
+            def p = GroovyPromise.of(CompletableFuture.supplyAsync { 10 })
+            def p2 = p.then { it * 3 }
+            assert p2.get() == 30
+        '''
+    }
+
+    @Test
+    void testGroovyPromiseThenCompose() {
+        assertScript '''
+            import groovy.concurrent.Awaitable
+            import groovy.concurrent.GroovyPromise
+            import java.util.concurrent.CompletableFuture
+
+            def p = GroovyPromise.of(CompletableFuture.supplyAsync { 5 })
+            def p2 = p.thenCompose { val ->
+                GroovyPromise.of(CompletableFuture.supplyAsync { val * 4 })
+            }
+            assert p2.get() == 20
+        '''
+    }
+
+    @Test
+    void testGroovyPromiseExceptionally() {
+        assertScript '''
+            import groovy.concurrent.GroovyPromise
+            import java.util.concurrent.CompletableFuture
+
+            def p = GroovyPromise.of(CompletableFuture.supplyAsync { throw new RuntimeException("fail") })
+            def recovered = p.exceptionally { t -> "recovered" }
+            assert recovered.get() == "recovered"
+        '''
+    }
+
+    @Test
+    void testAwaitableToCompletableFuture() {
+        assertScript '''
+            import groovy.concurrent.GroovyPromise
+            import java.util.concurrent.CompletableFuture
+
+            def p = GroovyPromise.of(CompletableFuture.completedFuture("interop"))
+            CompletableFuture<String> cf = p.toCompletableFuture()
+            assert cf.get() == "interop"
+        '''
+    }
+
+    // ==== AwaitableAdapterRegistry tests ====
+
+    @Test
+    void testAdapterRegistryWithCompletableFuture() {
+        assertScript '''
+            import groovy.concurrent.Awaitable
+            import groovy.concurrent.AwaitableAdapterRegistry
+            import java.util.concurrent.CompletableFuture
+
+            def cf = CompletableFuture.completedFuture("adapted")
+            Awaitable<String> a = AwaitableAdapterRegistry.toAwaitable(cf)
+            assert a.get() == "adapted"
+        '''
+    }
+
+    @Test
+    void testAdapterRegistryPassthroughAwaitable() {
+        assertScript '''
+            import groovy.concurrent.Awaitable
+            import groovy.concurrent.AwaitableAdapterRegistry
+
+            def original = Awaitable.of(42)
+            def adapted = AwaitableAdapterRegistry.toAwaitable(original)
+            assert adapted.is(original)
+        '''
+    }
+
+    @Test
+    void testAdapterRegistryCustomAdapter() {
+        assertScript '''
+            import groovy.concurrent.Awaitable
+            import groovy.concurrent.AwaitableAdapter
+            import groovy.concurrent.AwaitableAdapterRegistry
+            import groovy.concurrent.GroovyPromise
+            import java.util.concurrent.CompletableFuture
+
+            // Simulate a custom async type
+            class CustomPromise {
+                final String value
+                CustomPromise(String v) { this.value = v }
+            }
+
+            // Register custom adapter
+            AwaitableAdapterRegistry.register(new AwaitableAdapter() {
+                boolean supportsAwaitable(Class<?> type) { CustomPromise.isAssignableFrom(type) }
+                def <T> Awaitable<T> toAwaitable(Object source) {
+                    return Awaitable.of(((CustomPromise) source).value)
+                }
+            })
+
+            def custom = new CustomPromise("custom-value")
+            Awaitable<String> a = AwaitableAdapterRegistry.toAwaitable(custom)
+            assert a.get() == "custom-value"
+        '''
+    }
+
+    @Test
+    void testAwaitWithCustomAdaptedType() {
+        assertScript '''
+            import groovy.concurrent.Awaitable
+            import groovy.concurrent.AwaitableAdapter
+            import groovy.concurrent.AwaitableAdapterRegistry
+            import static groovy.transform.AsyncUtils.*
+
+            class Deferred {
+                final Object result
+                Deferred(Object r) { this.result = r }
+            }
+
+            AwaitableAdapterRegistry.register(new AwaitableAdapter() {
+                boolean supportsAwaitable(Class<?> type) { Deferred.isAssignableFrom(type) }
+                def <T> Awaitable<T> toAwaitable(Object source) {
+                    return Awaitable.of(((Deferred) source).result)
+                }
+            })
+
+            // await works with custom types via adapter
+            def d = new Deferred("deferred-value")
+            def result = await(d)
+            assert result == "deferred-value"
+        '''
+    }
+
+    // ==== for-await tests ====
+
+    @Test
+    void testForAwaitWithList() {
+        assertScript '''
+            import groovy.concurrent.Awaitable
+
+            class Processor {
+                @groovy.transform.Async
+                def processAll() {
+                    def results = []
+                    for await (item in [1, 2, 3, 4, 5]) {
+                        results << item * 2
+                    }
+                    return results
+                }
+            }
+
+            def p = new Processor()
+            assert p.processAll().get() == [2, 4, 6, 8, 10]
+        '''
+    }
+
+    @Test
+    void testForAwaitWithBreak() {
+        assertScript '''
+            import groovy.concurrent.Awaitable
+
+            class Processor {
+                @groovy.transform.Async
+                def findFirst() {
+                    for await (item in [10, 20, 30, 40]) {
+                        if (item > 20) {
+                            return item
+                        }
+                    }
+                    return -1
+                }
+            }
+
+            def p = new Processor()
+            assert p.findFirst().get() == 30
+        '''
+    }
+
+    @Test
+    void testForAwaitWithAsyncKeyword() {
+        assertScript '''
+            import groovy.concurrent.Awaitable
+
+            class Worker {
+                async def sumItems() {
+                    int sum = 0
+                    for await (item in [1, 2, 3]) {
+                        sum += item
+                    }
+                    return sum
+                }
+            }
+
+            def worker = new Worker()
+            assert worker.sumItems().get() == 6
+        '''
+    }
+
+    // ==== 'async' and 'await' as identifiers (backward compat) ====
+
+    @Test
+    void testAsyncAsVariableName() {
+        assertScript '''
+            def async = 42
+            assert async == 42
+        '''
+    }
+
+    @Test
+    void testAwaitAsVariableName() {
+        assertScript '''
+            def await = "hello"
+            assert await == "hello"
+        '''
+    }
+
+    @Test
+    void testAsyncAsMethodName() {
+        assertScript '''
+            class Compat {
+                def async() { return "compatible" }
+            }
+            assert new Compat().async() == "compatible"
+        '''
+    }
+
+    // ==== Combined async/await keyword with Awaitable ====
+
+    @Test
+    void testFullAsyncAwaitPipeline() {
+        assertScript '''
+            import groovy.concurrent.Awaitable
+            import java.util.concurrent.CompletableFuture
+
+            class DataService {
+                async def fetchUser(long id) {
+                    return [name: "User${id}"]
+                }
+
+                async def fetchOrders(long id) {
+                    return ["order1", "order2"]
+                }
+
+                async def getUserSummary(long id) {
+                    def user = await fetchUser(id)
+                    def orders = await fetchOrders(id)
+                    return [user: user, orders: orders]
+                }
+            }
+
+            def service = new DataService()
+            def summary = service.getUserSummary(1).get()
+            assert summary.user.name == "User1"
+            assert summary.orders.size() == 2
+        '''
+    }
+
+    @Test
+    void testAsyncAwaitWithExceptionRecovery() {
+        assertScript '''
+            import groovy.concurrent.Awaitable
+            import java.util.concurrent.CompletableFuture
+
+            class ResilientService {
+                async def fetchWithFallback() {
+                    try {
+                        return await CompletableFuture.supplyAsync { throw new RuntimeException("primary failed") }
+                    } catch (RuntimeException e) {
+                        return "fallback-value"
+                    }
+                }
+            }
+
+            def service = new ResilientService()
+            assert service.fetchWithFallback().get() == "fallback-value"
+        '''
+    }
+
+    @Test
+    void testAsyncAwaitMixedWithAnnotation() {
+        assertScript '''
+            import groovy.transform.Async
+            import groovy.concurrent.Awaitable
+            import java.util.concurrent.CompletableFuture
+
+            class MixedService {
+                // Annotation style
+                @Async
+                def annotationStyle() {
+                    return await CompletableFuture.supplyAsync { "annotation" }
+                }
+
+                // Keyword style
+                async def keywordStyle() {
+                    return await CompletableFuture.supplyAsync { "keyword" }
+                }
+
+                // They interoperate
+                async def combined() {
+                    def a = await annotationStyle()
+                    def b = await keywordStyle()
+                    return "${a}+${b}"
+                }
+            }
+
+            def svc = new MixedService()
+            assert svc.combined().get() == "annotation+keyword"
+        '''
+    }
+}
diff --git a/src/test/groovy/org/codehaus/groovy/transform/AsyncVirtualThreadTest.groovy b/src/test/groovy/org/codehaus/groovy/transform/AsyncVirtualThreadTest.groovy
new file mode 100644
index 0000000000..25930edd35
--- /dev/null
+++ b/src/test/groovy/org/codehaus/groovy/transform/AsyncVirtualThreadTest.groovy
@@ -0,0 +1,1115 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.codehaus.groovy.transform
+
+import org.junit.jupiter.api.Test
+
+import static groovy.test.GroovyAssert.assertScript
+
+/**
+ * Tests for virtual thread integration, executor configuration, exception
+ * handling consistency across async methods/closures/lambdas, and coverage
+ * improvements for the async/await feature.
+ *
+ * @since 6.0.0
+ */
+final class AsyncVirtualThreadTest {
+
+    // ---- Virtual thread detection and usage ----
+
+    @Test
+    void testVirtualThreadsAvailable() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+            // JDK 21+ should have virtual threads
+            def jdkVersion = Runtime.version().feature()
+            if (jdkVersion >= 21) {
+                assert isVirtualThreadsAvailable()
+            }
+        '''
+    }
+
+    @Test
+    void testAsyncMethodRunsOnVirtualThread() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+
+            class VTService {
+                async checkThread() {
+                    return Thread.currentThread().isVirtual()
+                }
+            }
+
+            if (isVirtualThreadsAvailable()) {
+                def service = new VTService()
+                def result = service.checkThread().get()
+                assert result == true
+            }
+        '''
+    }
+
+    @Test
+    void testAsyncClosureRunsOnVirtualThread() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+
+            if (isVirtualThreadsAvailable()) {
+                def awaitable = async { Thread.currentThread().isVirtual() }
+                assert await(awaitable) == true
+            }
+        '''
+    }
+
+    @Test
+    void testAsyncLambdaRunsOnVirtualThread() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+
+            if (isVirtualThreadsAvailable()) {
+                def asyncFn = async { x -> Thread.currentThread().isVirtual() }
+                def result = await(asyncFn(42))
+                assert result == true
+            }
+        '''
+    }
+
+    @Test
+    void testForAwaitRunsOnVirtualThread() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+            import groovy.concurrent.AsyncStream
+
+            class VTGenerator {
+                async generate() {
+                    yield return Thread.currentThread().isVirtual()
+                }
+            }
+
+            if (isVirtualThreadsAvailable()) {
+                def gen = new VTGenerator()
+                def stream = gen.generate()
+                def results = []
+                for await (item in stream) {
+                    results << item
+                }
+                assert results == [true]
+            }
+        '''
+    }
+
+    @Test
+    void testHighConcurrencyWithVirtualThreads() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+
+            class HighConcurrency {
+                async compute(int n) {
+                    Thread.sleep(10)
+                    return n * 2
+                }
+            }
+
+            if (isVirtualThreadsAvailable()) {
+                def svc = new HighConcurrency()
+                // Launch 1000 concurrent tasks — trivial with virtual threads
+                def awaitables = (1..1000).collect { svc.compute(it) }
+                def results = awaitAll(awaitables as Object[])
+                assert results.size() == 1000
+                assert results[0] == 2
+                assert results[999] == 2000
+            }
+        '''
+    }
+
+    // ---- Executor configuration ----
+
+    @Test
+    void testCustomExecutorOverride() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+            import java.util.concurrent.Executors
+            import java.util.concurrent.atomic.AtomicReference
+
+            def savedExecutor = getExecutor()
+            try {
+                def customPool = Executors.newFixedThreadPool(2, { r ->
+                    def t = new Thread(r)
+                    t.setName("custom-async-" + t.getId())
+                    t
+                })
+                setExecutor(customPool)
+
+                def awaitable = async {
+                    Thread.currentThread().getName()
+                }
+                def threadName = await(awaitable)
+                assert threadName.startsWith("custom-async-")
+            } finally {
+                setExecutor(savedExecutor)
+            }
+        '''
+    }
+
+    @Test
+    void testSetExecutorNullResetsToDefault() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+            import java.util.concurrent.Executors
+
+            def originalExecutor = getExecutor()
+            // Set a custom executor
+            setExecutor(Executors.newSingleThreadExecutor())
+            assert getExecutor() != originalExecutor
+            // Reset to null — should restore default
+            setExecutor(null)
+            def restored = getExecutor()
+            assert restored != null
+            // Verify it works
+            def awaitable = groovy.transform.AsyncUtils.async { 42 }
+            assert groovy.transform.AsyncUtils.await(awaitable) == 42
+            // Restore original
+            setExecutor(originalExecutor)
+        '''
+    }
+
+    @Test
+    void testAsyncMethodWithCustomExecutorField() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+            import java.util.concurrent.Executor
+            import java.util.concurrent.Executors
+
+            class CustomExecutorService {
+                static Executor myPool = Executors.newFixedThreadPool(1, { r ->
+                    def t = new Thread(r)
+                    t.setName("my-pool-thread")
+                    t
+                })
+
+                @Async(executor = "myPool")
+                def doWork() {
+                    return Thread.currentThread().getName()
+                }
+            }
+
+            def svc = new CustomExecutorService()
+            def result = svc.doWork().get()
+            assert result.startsWith("my-pool-thread")
+        '''
+    }
+
+    // ---- Exception handling consistency: async method vs closure vs lambda ----
+
+    @Test
+    void testCheckedExceptionConsistencyAcrossAsyncMethods() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+
+            class ExcService {
+                async failWithChecked() {
+                    throw new java.io.IOException("async method error")
+                }
+            }
+
+            def svc = new ExcService()
+            try {
+                await(svc.failWithChecked())
+                assert false : "Should have thrown"
+            } catch (java.io.IOException e) {
+                assert e.message == "async method error"
+            }
+        '''
+    }
+
+    @Test
+    void testCheckedExceptionConsistencyAcrossClosures() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+
+            def awaitable = async { throw new java.io.IOException("closure error") }
+            try {
+                await(awaitable)
+                assert false : "Should have thrown"
+            } catch (java.io.IOException e) {
+                assert e.message == "closure error"
+            }
+        '''
+    }
+
+    @Test
+    void testCheckedExceptionConsistencyAcrossLambdas() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+
+            def asyncFn = async { x -> throw new java.io.IOException("lambda error: ${x}") }
+            try {
+                await(asyncFn("test"))
+                assert false : "Should have thrown"
+            } catch (java.io.IOException e) {
+                assert e.message == "lambda error: test"
+            }
+        '''
+    }
+
+    @Test
+    void testRuntimeExceptionConsistencyAllForms() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+
+            // Form 1: async method
+            class Svc {
+                async failMethod() { throw new IllegalStateException("from method") }
+            }
+
+            // Form 2: async closure
+            def closure = async { throw new IllegalArgumentException("from closure") }
+
+            // Form 3: async lambda with params
+            def lambda = async { x -> throw new UnsupportedOperationException("from lambda") }
+
+            // All should throw the exact exception type (no wrapping)
+            try { await(new Svc().failMethod()); assert false }
+            catch (IllegalStateException e) { assert e.message == "from method" }
+
+            try { await(closure); assert false }
+            catch (IllegalArgumentException e) { assert e.message == "from closure" }
+
+            try { await(lambda("x")); assert false }
+            catch (UnsupportedOperationException e) { assert e.message == "from lambda" }
+        '''
+    }
+
+    @Test
+    void testErrorPropagationConsistencyAllForms() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+
+            class ErrorSvc {
+                async fail() { throw new StackOverflowError("method") }
+            }
+
+            // async method
+            try { await(new ErrorSvc().fail()); assert false }
+            catch (StackOverflowError e) { assert e.message == "method" }
+
+            // async closure
+            try { await(async { throw new StackOverflowError("closure") }); assert false }
+            catch (StackOverflowError e) { assert e.message == "closure" }
+
+            // async lambda
+            def fn = async { x -> throw new StackOverflowError("lambda") }
+            try { await(fn(1)); assert false }
+            catch (StackOverflowError e) { assert e.message == "lambda" }
+        '''
+    }
+
+    // ---- Async stream (yield return) consistency ----
+
+    @Test
+    void testYieldReturnConsistencyMethodVsClosure() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+
+            // Form 1: async method with yield return
+            class GenSvc {
+                async range(int n) {
+                    for (int i = 1; i <= n; i++) {
+                        yield return i
+                    }
+                }
+            }
+
+            // Form 2: async closure with yield return
+            def closureGen = async { for (int i = 1; i <= 3; i++) { yield return i * 10 } }
+
+            // Form 3: async lambda with yield return + params
+            def lambdaGen = async { n -> for (int i = 1; i <= n; i++) { yield return i * 100 } }
+
+            // Verify all produce correct streams
+            def methodResults = []
+            for await (item in new GenSvc().range(3)) { methodResults << item }
+            assert methodResults == [1, 2, 3]
+
+            def closureResults = []
+            for await (item in closureGen) { closureResults << item }
+            assert closureResults == [10, 20, 30]
+
+            def lambdaResults = []
+            for await (item in lambdaGen(3)) { lambdaResults << item }
+            assert lambdaResults == [100, 200, 300]
+        '''
+    }
+
+    @Test
+    void testYieldReturnExceptionConsistencyAllForms() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+
+            // async method generator with error
+            class FailGen {
+                async failing() {
+                    yield return 1
+                    throw new java.io.IOException("gen method error")
+                }
+            }
+
+            // async closure generator with error
+            def closureFailGen = async {
+                yield return 10
+                throw new java.io.IOException("gen closure error")
+            }
+
+            // async lambda generator with error
+            def lambdaFailGen = async { x ->
+                yield return x
+                throw new java.io.IOException("gen lambda error")
+            }
+
+            // All should propagate IOException through for-await
+            for (gen in [new FailGen().failing(), closureFailGen, lambdaFailGen(100)]) {
+                def items = []
+                try {
+                    for await (item in gen) { items << item }
+                    assert false : "Should have thrown"
+                } catch (java.io.IOException e) {
+                    assert e.message.contains("gen")
+                    assert items.size() == 1
+                }
+            }
+        '''
+    }
+
+    // ---- executeAsync / executeAsyncVoid (unified path) ----
+
+    @Test
+    void testExecuteAsyncWithCustomExecutor() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+            import java.util.concurrent.Executors
+
+            def pool = Executors.newSingleThreadExecutor({ r ->
+                def t = new Thread(r)
+                t.setName("exec-async-test")
+                t
+            })
+
+            def awaitable = executeAsync({ ->
+                Thread.currentThread().getName()
+            }, pool)
+            def result = await(awaitable)
+            assert result.startsWith("exec-async-test")
+            pool.shutdown()
+        '''
+    }
+
+    @Test
+    void testExecuteAsyncVoidWithCustomExecutor() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+            import java.util.concurrent.Executors
+            import java.util.concurrent.atomic.AtomicBoolean
+
+            def pool = Executors.newSingleThreadExecutor()
+            def executed = new AtomicBoolean(false)
+
+            def awaitable = executeAsyncVoid({ ->
+                executed.set(true)
+            }, pool)
+            def _v1 = await(awaitable)
+            assert executed.get()
+            pool.shutdown()
+        '''
+    }
+
+    @Test
+    void testAsyncVoidMethodReturnsAwaitable() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+            import groovy.concurrent.Awaitable
+            import java.util.concurrent.atomic.AtomicInteger
+
+            class VoidService {
+                static AtomicInteger counter = new AtomicInteger(0)
+
+                @Async
+                void increment() {
+                    counter.incrementAndGet()
+                }
+            }
+
+            def svc = new VoidService()
+            def awaitable = svc.increment()
+            assert awaitable instanceof Awaitable
+            def _v2 = await(awaitable)
+            assert VoidService.counter.get() == 1
+        '''
+    }
+
+    // ---- Edge cases and coverage improvements ----
+
+    @Test
+    void testAwaitNull() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+            def result = await((Object) null)
+            assert result == null
+        '''
+    }
+
+    @Test
+    void testAwaitAlreadyCompletedAwaitable() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+            import groovy.concurrent.Awaitable
+
+            def awaitable = Awaitable.of(42)
+            assert awaitable.isDone()
+            def result = await(awaitable)
+            assert result == 42
+        '''
+    }
+
+    @Test
+    void testAwaitFailedAwaitable() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+            import groovy.concurrent.Awaitable
+
+            def awaitable = Awaitable.failed(new RuntimeException("pre-failed"))
+            assert awaitable.isCompletedExceptionally()
+            try {
+                await(awaitable)
+                assert false
+            } catch (RuntimeException e) {
+                assert e.message == "pre-failed"
+            }
+        '''
+    }
+
+    @Test
+    void testAwaitCancelledAwaitable() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+            import groovy.concurrent.GroovyPromise
+            import java.util.concurrent.CompletableFuture
+            import java.util.concurrent.CancellationException
+
+            def cf = new CompletableFuture()
+            def awaitable = GroovyPromise.of(cf)
+            awaitable.cancel()
+            assert awaitable.isCancelled()
+            try {
+                await(awaitable)
+                assert false
+            } catch (CancellationException e) {
+                // expected
+            }
+        '''
+    }
+
+    @Test
+    void testAsyncWithReturnValue() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+
+            def awaitable = async { return "hello" }
+            assert await(awaitable) == "hello"
+        '''
+    }
+
+    @Test
+    void testAsyncWithNullReturnValue() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+
+            def awaitable = async { return null }
+            assert await(awaitable) == null
+        '''
+    }
+
+    @Test
+    void testMultipleConcurrentAsyncGenerators() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+
+            def gen1 = async { for (int i = 0; i < 5; i++) { yield return "A${i}" } }
+            def gen2 = async { for (int i = 0; i < 5; i++) { yield return "B${i}" } }
+
+            def results1 = []
+            def results2 = []
+            for await (item in gen1) { results1 << item }
+            for await (item in gen2) { results2 << item }
+            assert results1 == ["A0", "A1", "A2", "A3", "A4"]
+            assert results2 == ["B0", "B1", "B2", "B3", "B4"]
+        '''
+    }
+
+    @Test
+    void testEmptyAsyncStream() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+
+            class EmptyGen {
+                async empty() {
+                    // A generator must have yield return to be detected as one.
+                    // This has one but it's unreachable at runtime.
+                    if (false) yield return "unreachable"
+                }
+            }
+
+            def results = []
+            for await (item in new EmptyGen().empty()) {
+                results << item
+            }
+            assert results.isEmpty()
+        '''
+    }
+
+    @Test
+    void testAwaitableCompositionWithThen() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+            import groovy.concurrent.Awaitable
+
+            def original = async { 10 }
+            def doubled = original.then { it * 2 }
+            def result = await(doubled)
+            assert result == 20
+        '''
+    }
+
+    @Test
+    void testAwaitableCompositionWithThenCompose() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+            import groovy.concurrent.Awaitable
+
+            def first = async { 5 }
+            def chained = first.thenCompose { v -> async { v + 10 } }
+            def result = await(chained)
+            assert result == 15
+        '''
+    }
+
+    @Test
+    void testAwaitableExceptionally() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+            import groovy.concurrent.Awaitable
+
+            def failing = async { throw new RuntimeException("oops") }
+            def recovered = failing.exceptionally { e -> "recovered: ${e.message}" }
+            def result = await(recovered)
+            assert result == "recovered: oops"
+        '''
+    }
+
+    @Test
+    void testAwaitWithCompletionStage() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+            import java.util.concurrent.CompletableFuture
+            import java.util.concurrent.CompletionStage
+
+            CompletionStage stage = CompletableFuture.supplyAsync { "from stage" }
+            def result = await(stage)
+            assert result == "from stage"
+        '''
+    }
+
+    @Test
+    void testAwaitWithFuture() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+            import java.util.concurrent.Executors
+            import java.util.concurrent.Future
+
+            def pool = Executors.newSingleThreadExecutor()
+            Future future = pool.submit({ "from future" } as java.util.concurrent.Callable)
+            def result = await(future)
+            assert result == "from future"
+            pool.shutdown()
+        '''
+    }
+
+    @Test
+    void testAwaitAllWithMixedTypes() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+            import groovy.concurrent.Awaitable
+            import java.util.concurrent.CompletableFuture
+
+            def a1 = async { 1 }
+            def a2 = CompletableFuture.supplyAsync { 2 }
+            def a3 = Awaitable.of(3)
+
+            def results = awaitAll(a1, a2, a3)
+            assert results == [1, 2, 3]
+        '''
+    }
+
+    @Test
+    void testAwaitAnyReturnsFirst() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+            import groovy.concurrent.Awaitable
+
+            def fast = Awaitable.of("fast")
+            def slow = async { Thread.sleep(500); "slow" }
+
+            def result = awaitAny(fast, slow)
+            assert result == "fast"
+        '''
+    }
+
+    @Test
+    void testAwaitAllSettledMixed() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+
+            def success = async { "ok" }
+            def failure = async { throw new RuntimeException("fail") }
+
+            def results = awaitAllSettled(success, failure)
+            assert results.size() == 2
+            assert results[0].isSuccess()
+            assert results[0].getValue() == "ok"
+            assert results[1].isFailure()
+            assert results[1].getError().message == "fail"
+        '''
+    }
+
+    @Test
+    void testAsyncMethodWithPrimitiveReturnType() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+
+            class PrimService {
+                @Async
+                int computeInt() { return 42 }
+
+                @Async
+                boolean checkBool() { return true }
+
+                @Async
+                double computeDouble() { return 3.14 }
+            }
+
+            def svc = new PrimService()
+            assert await(svc.computeInt()) == 42
+            assert await(svc.checkBool()) == true
+            assert Math.abs(await(svc.computeDouble()) - 3.14) < 0.001
+        '''
+    }
+
+    @Test
+    void testDeepUnwrapNestedExceptions() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+            import java.util.concurrent.CompletionException
+            import java.util.concurrent.ExecutionException
+
+            // Create deeply nested exception chain
+            def original = new java.io.IOException("deep")
+            def wrapped = new CompletionException(new ExecutionException(
+                new java.lang.reflect.UndeclaredThrowableException(
+                    new java.lang.reflect.InvocationTargetException(original))))
+
+            def cf = new java.util.concurrent.CompletableFuture()
+            cf.completeExceptionally(wrapped)
+
+            try {
+                await(cf)
+                assert false
+            } catch (java.io.IOException e) {
+                assert e.message == "deep"
+            }
+        '''
+    }
+
+    @Test
+    void testAsyncWithNestedAwait() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+
+            class NestedService {
+                async inner(int v) { return v * 2 }
+
+                async outer(int v) {
+                    def intermediate = await inner(v)
+                    return await inner(intermediate)
+                }
+            }
+
+            def svc = new NestedService()
+            def result = await(svc.outer(5))
+            assert result == 20  // 5 * 2 * 2
+        '''
+    }
+
+    @Test
+    void testAsyncClosureWithNestedAwait() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+
+            def inner = async { x -> x + 1 }
+            def outer = async {
+                def v1 = await(inner(10))
+                def v2 = await(inner(v1))
+                return v2
+            }
+            assert await(outer) == 12  // 10 + 1 + 1
+        '''
+    }
+
+    @Test
+    void testParallelAwaitInAsyncMethod() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+            import java.util.concurrent.CompletableFuture
+
+            class ParallelService {
+                CompletableFuture<Integer> compute(int v) {
+                    return CompletableFuture.supplyAsync {
+                        Thread.sleep(50)
+                        return v * 2
+                    }
+                }
+
+                async parallel() {
+                    def f1 = compute(1)
+                    def f2 = compute(2)
+                    def f3 = compute(3)
+                    return await(f1) + await(f2) + await(f3)
+                }
+            }
+
+            def svc = new ParallelService()
+            long start = System.currentTimeMillis()
+            def result = await(svc.parallel())
+            long elapsed = System.currentTimeMillis() - start
+            assert result == 12  // 2 + 4 + 6
+            assert elapsed < 500  // parallel, not sequential
+        '''
+    }
+
+    @Test
+    void testToAsyncStreamConversion() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+            import groovy.concurrent.AsyncStream
+            import groovy.concurrent.Awaitable
+            import groovy.concurrent.AwaitableAdapter
+            import groovy.concurrent.AwaitableAdapterRegistry
+
+            // Register an adapter for List that provides an AsyncStream
+            AwaitableAdapterRegistry.register(new AwaitableAdapter() {
+                boolean supportsAwaitable(Class type) { return false }
+                Awaitable toAwaitable(Object source) { return null }
+                boolean supportsAsyncStream(Class type) { return List.isAssignableFrom(type) }
+                AsyncStream toAsyncStream(Object source) {
+                    def list = (List) source
+                    def iter = list.iterator()
+                    return new AsyncStream() {
+                        def current
+                        Awaitable<Boolean> moveNext() {
+                            if (iter.hasNext()) {
+                                current = iter.next()
+                                return Awaitable.of(true)
+                            }
+                            return Awaitable.of(false)
+                        }
+                        Object getCurrent() { return current }
+                    }
+                }
+            })
+
+            def results = []
+            def stream = toAsyncStream([10, 20, 30])
+            for await (item in stream) {
+                results << item
+            }
+            assert results == [10, 20, 30]
+        '''
+    }
+
+    @Test
+    void testAwaitObjectDispatchesToCorrectOverload() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+            import groovy.concurrent.Awaitable
+            import java.util.concurrent.CompletableFuture
+
+            // Test Object overload dispatch for each type
+            Object a = Awaitable.of("awaitable")
+            Object b = CompletableFuture.completedFuture("cf")
+            Object c = CompletableFuture.completedFuture("stage") as java.util.concurrent.CompletionStage
+
+            assert await(a) == "awaitable"
+            assert await(b) == "cf"
+            assert await(c) == "stage"
+        '''
+    }
+
+    @Test
+    void testAsyncMethodWithParameters() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+
+            class ParamService {
+                async add(int a, int b) {
+                    return a + b
+                }
+
+                async greet(String name) {
+                    return "Hello, ${name}!"
+                }
+            }
+
+            def svc = new ParamService()
+            assert await(svc.add(3, 4)) == 7
+            assert await(svc.greet("Groovy")) == "Hello, Groovy!"
+        '''
+    }
+
+    @Test
+    void testAsyncChainedMethods() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+            import groovy.concurrent.Awaitable
+
+            class Pipeline {
+                async step1() { return 1 }
+
+                async step2(Awaitable<Integer> input) {
+                    return await(input) + 10
+                }
+
+                async step3(Awaitable<Integer> input) {
+                    return await(input) * 2
+                }
+            }
+
+            def p = new Pipeline()
+            def r1 = p.step1()
+            def r2 = p.step2(r1)
+            def r3 = p.step3(r2)
+            assert r3.get() == 22
+        '''
+    }
+
+    @Test
+    void testAwaitSyntaxWithBinaryOperations() {
+        assertScript '''
+            import groovy.transform.Async
+            import static groovy.transform.AsyncUtils.*
+            import groovy.concurrent.Awaitable
+
+            class MathService {
+                async compute(int v) { return v }
+            }
+
+            def svc = new MathService()
+            // Test that await properly integrates with binary operators
+            def a = svc.compute(10)
+            def b = svc.compute(20)
+            def r1 = await(a)
+            def r2 = await(b)
+            assert r1 + r2 == 30
+        '''
+    }
+
+    @Test
+    void testAsyncClosureWithoutYieldReturnIsNotGenerator() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+
+            // Closure with no yield return produces an Awaitable (not AsyncStream)
+            def result = async { 42 }
+            assert await(result) == 42
+
+            def resultNull = async { /* empty body */ }
+            assert await(resultNull) == null
+        '''
+    }
+
+    @Test
+    void testGroovyPromiseToString() {
+        assertScript '''
+            import groovy.concurrent.GroovyPromise
+            import groovy.concurrent.Awaitable
+            import java.util.concurrent.CompletableFuture
+
+            def pending = new CompletableFuture()
+            def promise = GroovyPromise.of(pending)
+            assert promise.toString() == "GroovyPromise{pending}"
+
+            pending.complete(42)
+            assert promise.toString() == "GroovyPromise{completed}"
+
+            def failed = GroovyPromise.of(CompletableFuture.failedFuture(new RuntimeException()))
+            assert failed.toString() == "GroovyPromise{failed}"
+        '''
+    }
+
+    @Test
+    void testGroovyPromiseGetWithTimeout() {
+        assertScript '''
+            import groovy.concurrent.GroovyPromise
+            import static groovy.transform.AsyncUtils.*
+            import java.util.concurrent.TimeUnit
+            import java.util.concurrent.TimeoutException
+
+            def awaitable = async { Thread.sleep(5000); "never" }
+            try {
+                awaitable.get(50, TimeUnit.MILLISECONDS)
+                assert false : "Should have timed out"
+            } catch (TimeoutException e) {
+                // expected
+            }
+        '''
+    }
+
+    // ---- for-await null safety ----
+
+    @Test
+    void testForAwaitNullSkipsLoop() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+
+            class Gen {
+                async static test() {
+                    def results = []
+                    for await (item in null) {
+                        results << item
+                    }
+                    return results
+                }
+            }
+            assert Gen.test().get() == []
+        '''
+    }
+
+    @Test
+    void testForAwaitNullWithExistingItems() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+
+            class Gen {
+                async static test() {
+                    def results = []
+                    // Normal stream works
+                    def stream = async { yield return 42 }
+                    for await (item in stream) {
+                        results << item
+                    }
+                    // Null stream skips
+                    for await (item in null) {
+                        results << "should not appear"
+                    }
+                    return results
+                }
+            }
+            assert Gen.test().get() == [42]
+        '''
+    }
+
+    @Test
+    void testForAwaitEmptyStream() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+            import groovy.concurrent.AsyncStream
+
+            class Gen {
+                async static test() {
+                    def results = []
+                    for await (item in AsyncStream.empty()) {
+                        results << item
+                    }
+                    return results
+                }
+            }
+            assert Gen.test().get() == []
+        '''
+    }
+
+    // ---- ThreadLocal elimination verification ----
+
+    @Test
+    void testYieldReturnNoThreadLocal() {
+        assertScript '''
+            import static groovy.transform.AsyncUtils.*
+
+            // Verify that yield return works without ThreadLocal
+            // by running many concurrent generators simultaneously
+            class Gen {
+                async static generate(int id, int count) {
+                    for (i in 1..count) {
+                        yield return "${id}:${i}"
+                    }
+                }
+            }
+
+            def streams = (1..10).collect { Gen.generate(it, 5) }
+            def allResults = []
+            for (stream in streams) {
+                def items = []
+                for await (item in stream) {
+                    items << item
+                }
+                allResults << items
+            }
+
+            // Each generator produces exactly its items
+            for (i in 0..9) {
+                def expected = (1..5).collect { "${i+1}:${it}" }
+                assert allResults[i] == expected
+            }
+        '''
+    }
+
+    @Test
+    void testYieldReturnOutsideAsyncContextFails() {
+        assertScript '''
+            // yield return outside async context should fail at runtime
+            try {
+                groovy.transform.AsyncUtils.yieldReturn("oops")
+                assert false : "expected exception"
+            } catch (IllegalStateException e) {
+                assert e.message.contains("yield return")
+            }
+        '''
+    }
+}
diff --git a/versions.properties b/versions.properties
index 17c2aa09f6..af78e7ae37 100644
--- a/versions.properties
+++ b/versions.properties
@@ -51,6 +51,8 @@ logback=1.5.27
 openbeans=1.0.2
 picocli=4.7.7
 qdox=2.2.0
+reactor=3.7.3
+rxjava3=3.1.10
 slf4j=2.0.17
 # running with Groovy 6 can be allowed with -Dspock.iKnowWhatImDoing.disableGroovyVersionCheck=true
 spock=2.4-groovy-5.0