From 545948eb183924bb45c390f4e3c0233bce6f473f Mon Sep 17 00:00:00 2001 From: Nathan Rauh Date: Mon, 20 Sep 2021 16:02:54 -0500 Subject: [PATCH 1/6] Issue #43 asynchronous methods annotation Signed-off-by: Nathan Rauh --- api/pom.xml | 10 + .../jakarta/enterprise/concurrent/Async.java | 304 ++++++++++++++++ .../enterprise/concurrent/AsyncTest.java | 341 ++++++++++++++++++ .../main/asciidoc/jakarta-concurrency.adoc | 156 ++++++++ 4 files changed, 811 insertions(+) create mode 100644 api/src/main/java/jakarta/enterprise/concurrent/Async.java create mode 100644 api/src/test/java/jakarta/enterprise/concurrent/AsyncTest.java diff --git a/api/pom.xml b/api/pom.xml index cca96726..e35367aa 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -336,6 +336,16 @@ jakarta.annotation-api 2.0.0 + + jakarta.enterprise + jakarta.enterprise.cdi-api + 4.0.0.Alpha2 + + + jakarta.interceptor + jakarta.interceptor-api + 2.0.0 + jakarta.transaction jakarta.transaction-api diff --git a/api/src/main/java/jakarta/enterprise/concurrent/Async.java b/api/src/main/java/jakarta/enterprise/concurrent/Async.java new file mode 100644 index 00000000..001c00e1 --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/concurrent/Async.java @@ -0,0 +1,304 @@ +/* + * Copyright (c) 2021 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package jakarta.enterprise.concurrent; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.concurrent.CompletableFuture; + +import jakarta.enterprise.util.Nonbinding; +import jakarta.interceptor.InterceptorBinding; + +/** + * Annotates a CDI managed bean method (or CDI managed bean class containing suitable methods) + * to run asynchronously. + *

+ * The Jakarta EE Product Provider runs the method on a {@link ManagedExecutorService} + * and returns to the caller a {@link java.util.concurrent.CompletableFuture CompletableFuture} + * that is backed by the same ManagedExecutorService + * to represent the execution of the method. The ManagedExecutorService + * is the default asynchronous execution facility for the CompletableFuture + * and and all dependent stages that are created from those, and so on, + * as defined by the ManagedExecutorService JavaDoc API. + * The Jakarta EE Product Provider makes this CompletableFuture available + * to the asynchronous method implementation via the + * {@link Result#getFuture Async.Result.getFuture} and + * {@link Result#complete Async.Result.complete} methods. + *

+ * For example, + * + *

+ * {@literal @}Async
+ * public CompletableFuture{@literal } hoursWorked(LocalDate from, LocalDate to) {
+ *     // Application component's context is made available to the async method,
+ *     try (Connection con = ((DataSource) InitialContext.doLookup(
+ *         "java:comp/env/jdbc/timesheetDB")).getConnection()) {
+ *         ...
+ *         return Async.Result.complete(total);
+ *     } catch (NamingException | SQLException x) {
+ *         throw new CompletionException(x);
+ *     }
+ * }
+ * 
+ * + * with usage, + * + *
+ * hoursWorked(mon, fri).thenAccept(total {@literal ->} {
+ *     // Application component's context is made available to dependent stage actions,
+ *     DataSource ds = InitialContext.doLookup(
+ *         "java:comp/env/jdbc/payrollDB");
+ *     ...
+ * });
+ * 
+ * + * When the asynchronous method implementation returns a different + * CompletableFuture instance, the Jakarta EE Product Provider + * uses the completion of that instance to complete the CompletableFuture + * that the Jakarta EE Product Provider returns to the caller, + * completing it with the same result or exception. + *

+ * For example, + * + *

+ * {@literal @}Async
+ * public CompletableFuture{@literal >} findSingleLayoverFlights(Location source, Location dest) {
+ *     try {
+ *         ManagedExecutorService executor = InitialContext.doLookup(
+ *             "java:comp/DefaultManagedExecutorService");
+ *
+ *         return executor.supplyAsync(source::flightsFrom)
+ *                        .thenCombine(executor.completedFuture(dest.flightsTo()),
+ *                                     Itinerary::sourceMatchingDest);
+ *     } catch (NamingException x) {
+ *         throw new CompletionException(x);
+ *     }
+ * }
+ * 
+ * + * with usage, + * + *
+ * findSingleLayoverFlights(RST, DEN).thenApply(Itinerary::sortByPrice);
+ * 
+ * + *

+ * When annotating asynchronous methods at the method level, methods + * can have any of the following return types, with all other return types resulting in + * {@link java.lang.UnsupportedOperationException UnsupportedOperationException}: + *

    + *
  • {@link java.util.concurrent.CompletableFuture CompletableFuture}
  • + *
  • {@link java.util.concurrent.CompletionStage CompletionStage}
  • + *
  • void
  • + *
+ *

+ * When annotating asynchronous methods at the class level, methods with + * any of the following return types are considered asynchronous methods, + * whereas methods with other return types are treated as normal + * (non-asynchronous) methods: + *

    + *
  • {@link java.util.concurrent.CompletableFuture CompletableFuture}
  • + *
  • {@link java.util.concurrent.CompletionStage CompletionStage}
  • + *
+ *

+ * If the Async annotation is present at both the method and class + * level, the annotation that is specified at the method level takes precedence. + *

+ * Exceptions that are raised by asynchronous methods are not raised directly + * to the caller because the method runs asynchronously to the caller. + * Instead, the CompletableFuture that represents the result + * is completed with the raised exception. Asynchronous methods are + * discouraged from raising checked exceptions because checked exceptions + * force the caller to write exception handling code that is unreachable. + * When a checked exception occurs, the asynchronous method implementation + * can flow the exception back to the resulting CompletableFuture + * either by raising a + * {@link java.util.concurrent.CompletionException CompletionException} + * with the original exception as the cause, or it can take the equivalent + * approach of exceptionally completing the CompletableFuture, using + * {@link java.util.concurrent.CompletableFuture#completeExceptionally completeExceptionally} + * to supply the original exception as the cause. + *

+ * Except where otherwise stated, the Jakarta EE Product Provider raises + * {@link java.util.concurrent.RejectedExecutionException RejectedExecutionException} + * upon invocation of the asynchronous method if evident upfront that it cannot + * be accepted, for example if the JNDI name is not valid or points to something + * other than a managed executor resource. If determined at a later point that the + * asynchronous method cannot run (for example, if unable to establish thread context), + * then the Jakarta EE Product Provider completes the CompletableFuture + * exceptionally with {@link java.util.concurrent.CancellationException CancellationException}, + * and chains a cause exception if there is any. + *

+ * The Jakarta EE Product Provider must assign the interceptor for asynchronous methods + * to have priority of Interceptor.Priority.PLATFORM_BEFORE + 5. + * Interceptors with a lower priority, such as Transactional, must run on + * the thread where the asynchronous method executes, rather than on the submitting thread. + * When an asynchronous method is annotated as Transactional, + * the transactional types TxType.REQUIRES_NEW and + * TxType.NOT_SUPPORTED can be used. All other transaction attributes must + * result in {@link java.lang.UnsupportedOperationException UnsupportedOperationException} + * upon invocation of the asynchronous method. + * + * @since 3.0 + */ +// TODO the above restrictions on Transactional interceptors could be eliminated +// if transaction context propagation is later added to the spec. +@Documented +@Inherited +@InterceptorBinding +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.TYPE }) +public @interface Async { + /** + * JNDI name of a {@link ManagedExecutorService} or {@link ManagedScheduledExecutorService} + * upon which to run the asynchronous method. + *

+ * The default value is the JNDI name of the built-in ManagedExecutorService + * that is provided by the Jakarta EE platform provider:
+ * java:comp/DefaultManagedExecutorService + * + * @return managed executor service JNDI name. + */ + @Nonbinding + String executor() default "java:comp/DefaultManagedExecutorService"; + + /** + * Mechanism by which the Jakarta EE Product Provider makes available + * to the asynchronous method implementation the same + * {@link java.util.concurrent.CompletableFuture CompletableFuture} + * instance that the Jakarta EE Product Provider supplies to the caller + * of the asynchronous method. + *

+ * Before invoking the asynchronous method implementation on a thread, + * the Jakarta EE Product Provider invokes the {@link #setFuture} method + * which makes available to the asynchronous method implementation + * the same CompletableFuture that the Jakarta EE Product Provider + * returns to the caller. + *

+ * The asynchronous method implementation invokes the {@link #getFuture} method + * to obtain the same CompletableFuture that the + * Jakarta EE Product Provider returns to the caller. + * The asynchronous method implementation can choose to complete + * this future (normally or exceptionally) or otherwise arrange for its + * completion, for example upon completion of a pipeline of completion stages. + * Having this same CompletableFuture also enables the asynchronous + * method implementation to determine if the caller has forcibly completed + * (such as by cancellation or any other means) the CompletableFuture, + * in which case the asynchronous method implementation could decide to end + * immediately rather than continue processing. + *

+ * For example, + * + *

+     * {@literal @}Async
+     * public CompletableFuture{@literal } hoursWorked(LocalDateTime from, LocalDateTime to) {
+     *     CompletableFuture{@literal } future = Async.Result.getFuture();
+     *     if (future.isDone())
+     *         return future;
+     *
+     *     try (Connection con = ((DataSource) InitialContext.doLookup(
+     *         "java:comp/env/jdbc/timesheetDB")).getConnection()) {
+     *         ...
+     *         for (ResultSet result = stmt.executeQuery(); result.next() {@literal &&} !future.isDone(); )
+     *             ...
+     *         future.complete(total);
+     *     } catch (NamingException | SQLException x) {
+     *         future.completeExceptionally(x);
+     *     }
+     *     return future;
+     * }
+     * 
+ * + * After the asynchronous method completes, the Jakarta EE Product Provider + * invokes the {@link #setFuture} method with a null value + * to clear it from the thread. + * + * @since 3.0 + */ + public static class Result { + private static final ThreadLocal> futures = new ThreadLocal>(); + + /** + * Completes the {@link java.util.concurrent.CompletableFuture CompletableFuture} + * instance that the Jakarta EE Product Provider supplies to the caller of the + * asynchronous method. + *

+ * This method must only be invoked by the asynchronous method implementation. + * + * @param type of result returned by the asynchronous method's CompletableFuture. + * @param result result with which to complete the asynchronous method's CompletableFuture. + * @return the same CompletableFuture that the container returns to the caller. + * @throws IllegalStateException if the CompletableFuture for an asynchronous + * method is not present on the thread. + */ + public static CompletableFuture complete(T result) { + @SuppressWarnings("unchecked") + CompletableFuture future = (CompletableFuture) futures.get(); + if (future == null) + throw new IllegalStateException(); + future.complete(result); + return future; + } + + /** + * Obtains the same {@link java.util.concurrent.CompletableFuture CompletableFuture} + * instance that the Jakarta EE Product Provider supplies to the caller of the + * asynchronous method. + *

+ * This method must only be invoked by the asynchronous method implementation. + * + * @param type of result returned by the asynchronous method's CompletableFuture. + * @return the same CompletableFuture that the container returns to the caller. + * @throws IllegalStateException if the CompletableFuture for an asynchronous + * method is not present on the thread. + */ + public static CompletableFuture getFuture() { + @SuppressWarnings("unchecked") + CompletableFuture future = (CompletableFuture) futures.get(); + if (future == null) + throw new IllegalStateException(); + return future; + } + + /** + * Before invoking the asynchronous method implementation on a thread, + * the Jakarta EE Product Provider invokes this method to make available + * to the asynchronous method implementation the same CompletableFuture + * that the Jakarta EE Product Provider returns to the caller. + *

+ * After the asynchronous method completes, the Jakarta EE Product Provider + * invokes this method with a null value + * to clear it from the thread. + *

+ * This method must only be invoked by the Jakarta EE Product Provider. + * + * @param type of result returned by the asynchronous method's CompletableFuture. + * @param future CompletableFuture that the container returns to the caller, + * or null to clear it. + */ + public static void setFuture(CompletableFuture future) { + if (future == null) + futures.remove(); + else + futures.set(future); + } + } +} \ No newline at end of file diff --git a/api/src/test/java/jakarta/enterprise/concurrent/AsyncTest.java b/api/src/test/java/jakarta/enterprise/concurrent/AsyncTest.java new file mode 100644 index 00000000..143d3b15 --- /dev/null +++ b/api/src/test/java/jakarta/enterprise/concurrent/AsyncTest.java @@ -0,0 +1,341 @@ +/* + * Copyright (c) 2021 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package jakarta.enterprise.concurrent; + +import static org.junit.Assert.*; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.CompletionStage; + +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.sql.DataSource; + +import org.junit.Test; + +/** + * This test includes the code examples from the specification and JavaDoc + * for asychronous methods, verifying that they compile. + * The Async.Result class is also tested. + */ +@Async(executor = "java:module/env/concurrent/class-level-async-executor") +public class AsyncTest { + /** + * Example from the section on Asynchronous Methods in the Concurrency spec. + */ + @Async(executor = "java:module/env/concurrent/myExecutorRef") + public CompletableFuture> findSimilar(Cart cart, History h) { + Set combined = new LinkedHashSet(); + for (Item item : cart.items()) + combined.addAll(item.similar()); + for (Item item : h.recentlyViewed(3)) + combined.addAll(item.similar()); + combined.removeAll(cart.items()); + + try (Connection con = ((DataSource) InitialContext.doLookup( + "java:comp/env/jdbc/ds1")).getConnection()) { + PreparedStatement stmt = con.prepareStatement("..."); + for (Item item : combined) { + // ... Remove if the similar item is unavailable + } + } catch (NamingException | SQLException x) { + throw new CompletionException(x); + } + return Async.Result.complete(combined); + } + + /** + * Second example from the Async class JavaDoc. + */ + @Async + public CompletableFuture> findSingleLayoverFlights(Location source, Location dest) { + try { + ManagedExecutorService executor = InitialContext.doLookup( + "java:comp/DefaultManagedExecutorService"); + + return executor.supplyAsync(source::flightsFrom) + .thenCombine(executor.completedFuture(dest.flightsTo()), + Itinerary::destMatchingSource); + } catch (NamingException x) { + throw new CompletionException(x); + } + } + + /** + * First example from the Async class JavaDoc. + */ + @Async + public CompletableFuture hoursWorked(LocalDate from, LocalDate to) { + // Application component's context is made available to the async method, + try (Connection con = ((DataSource) InitialContext.doLookup( + "java:comp/env/jdbc/timesheetDB")).getConnection()) { + // ... + double total = 40.0; // added to make the example compile + return Async.Result.complete(total); + } catch (NamingException | SQLException x) { + throw new CompletionException(x); + } + } + + /** + * Example from the Async.Result class JavaDoc. + */ + @Async + public CompletableFuture hoursWorked(LocalDateTime from, LocalDateTime to) { + CompletableFuture future = Async.Result.getFuture(); + if (future.isDone()) + return future; + + try (Connection con = ((DataSource) InitialContext.doLookup( + "java:comp/env/jdbc/timesheetDB")).getConnection()) { + PreparedStatement stmt = con.prepareStatement("SQL"); // added to make the example compile + // ... + for (ResultSet result = stmt.executeQuery(); result.next() && !future.isDone(); ) + ; // ... + double total = 40.0; // added to make the example compile + future.complete(total); + } catch (NamingException | SQLException x) { + future.completeExceptionally(x); + } + return future; + } + + /** + * Used by Concurrency spec example code under Asynchronous Methods section. + * This code doesn't do anything; we only need the method signatures to verify compilation of the example. + */ + static class Cart { + public List items() { + return Collections.emptyList(); + } + } + + /** + * Used by Concurrency spec example code under Asynchronous Methods section. + * This code doesn't do anything; we only need the method signatures to verify compilation of the example. + */ + static class Customer { + public Cart getCart() { + return new Cart(); + } + + public History getHistory() { + return new History(); + } + } + + /** + * Used by Concurrency spec example code under Asynchronous Methods section. + * This code doesn't do anything; we only need the method signatures to verify compilation of the example. + */ + static class History { + public Set recentlyViewed(int max) { + return Collections.emptySet(); + } + } + + /** + * Used by Concurrency spec example code under Asynchronous Methods section. + * This code doesn't do anything; we only need the method signatures to verify compilation of the example. + */ + static class Item { + public Set similar() { + return Collections.emptySet(); + } + } + + /** + * Used by example from Async class JavaDoc. + * This code doesn't do anything; we only need the method signatures to verify compilation of the example. + */ + static class Itinerary { + static List destMatchingSource(List toDest, List fromSource) { + return Collections.emptyList(); + } + static List sortByPrice(List list) { + return list; + } + } + + /** + * Used by example from Async class JavaDoc. + * This code doesn't do anything; we only need the method signatures to verify compilation of the example. + */ + static class Location { + List flightsFrom() { + return Collections.emptyList(); + } + List flightsTo() { + return Collections.emptyList(); + } + } + + /** + * Verify that the Async annotation can be configured at class level + * and that its executor field is populated with the specified value. + */ + @Test + public void testAsyncClassLevelAnnotation() throws Exception { + Async anno = AsyncTest.class.getAnnotation(Async.class); + assertNotNull(anno); + assertEquals("java:module/env/concurrent/class-level-async-executor", anno.executor()); + } + + /** + * Verify that the first usage example in the Async class JavaDoc compiles. + */ + @Test + public void testAsyncJavaDocUsageExample1() { + LocalDateTime mon = LocalDateTime.of(2021, 9, 13, 0, 0, 0); + LocalDateTime fri = LocalDateTime.of(2021, 9, 17, 23, 59, 59); + + // Normally, the Jakarta EE Product Provider would do this on the same + // thread where the asynchronous method executes, just before it starts. + CompletableFuture future = new CompletableFuture(); + Async.Result.setFuture(future); + + // There is no interceptor when running these tests, so this won't actually run async. + try { + hoursWorked(mon, fri).thenAccept(total -> { + try { + DataSource ds = InitialContext.doLookup( + "java:comp/env/jdbc/payrollDB"); + // ... + } catch (NamingException x) { + throw new CompletionException(x); + } + }); + } finally { + // Normally, the Jakarta EE Product Provider would do this on the same + // thread where the asynchronous method executes, just after it ends. + Async.Result.setFuture(null); + } + + // Naming lookup will have failed, + assertTrue(future.isCompletedExceptionally()); + } + + /** + * Verify that the second usage example in the Async class JavaDoc compiles. + */ + @Test + public void testAsyncJavaDocUsageExample2() { + Location RST = new Location(); + Location DEN = new Location(); + + try { + CompletableFuture> leastToMostExpensive = + findSingleLayoverFlights(RST, DEN).thenApply(Itinerary::sortByPrice); + } catch (CompletionException x) { + // Expected in these tests because this is no interceptor to run the async method on another thread + } + } + + /** + * Verify that the Async annotation can be configured at method level + * and that its executor field defaults to the JNDI name of the + * built-in ManagedExecutorService that is provided by the Jakarta EE platform. + */ + @Test + public void testAsyncMethodLevelAnnotation() throws Exception { + Async anno = AsyncTest.class.getMethod("hoursWorked", LocalDate.class, LocalDate.class) + .getAnnotation(Async.class); + assertNotNull(anno); + assertEquals("java:comp/DefaultManagedExecutorService", anno.executor()); + } + + /** + * Invoke all of the static methods of Async.Result. + */ + @Test + public void testAsyncResult() { + try { + fail("Must not be present by default: " + Async.Result.getFuture()); + } catch (IllegalStateException x) { + // Pass - detected that this was not invoked from an async method + } + + CompletableFuture stringFuture = new CompletableFuture(); + Async.Result.setFuture(stringFuture); + assertEquals(stringFuture, Async.Result.getFuture()); + Async.Result.setFuture(null); + try { + fail("Must not be present after removing: " + Async.Result.getFuture()); + } catch (IllegalStateException x) { + // Pass - detected that this was not invoked from an async method + } + + CompletableFuture intFuture = new CompletableFuture(); + Async.Result.setFuture(intFuture); + assertEquals(intFuture, Async.Result.complete(100)); + Async.Result.setFuture(null); + try { + fail("Must not be present after removing: " + Async.Result.getFuture()); + } catch (IllegalStateException x) { + // Pass - detected that this was not invoked from an async method + } + + CompletableFuture booleanFuture = new CompletableFuture(); + Async.Result.setFuture(booleanFuture); + assertEquals(booleanFuture, Async.Result.getFuture()); + assertEquals(booleanFuture, Async.Result.complete(true)); + Async.Result.setFuture(null); + try { + fail("Must not be present after removing: " + Async.Result.getFuture()); + } catch (IllegalStateException x) { + // Pass - detected that this was not invoked from an async method + } + + CompletableFuture unusedFuture = new CompletableFuture(); + Async.Result.setFuture(unusedFuture); + Async.Result.setFuture(null); + try { + fail("Must not be present after removing: " + Async.Result.getFuture()); + } catch (IllegalStateException x) { + // Pass - detected that this was not invoked from an async method + } + } + + /** + * Verify that the usage example in the Asynchronous Methods section of the + * Concurrency spec compiles. + */ + @Test + public void testAsyncUsageExampleFromSpec() throws Exception { + Customer cust = new Customer(); + + try { + findSimilar(cust.getCart(), cust.getHistory()) + .thenAccept(recommended -> { + // ... Update page with recommendations + }).join(); + } catch (CompletionException x) { + // Naming lookup will have failed because this doesn't run in a real server. + } + } +} diff --git a/specification/src/main/asciidoc/jakarta-concurrency.adoc b/specification/src/main/asciidoc/jakarta-concurrency.adoc index 1248d241..196aa3a0 100644 --- a/specification/src/main/asciidoc/jakarta-concurrency.adoc +++ b/specification/src/main/asciidoc/jakarta-concurrency.adoc @@ -2302,3 +2302,159 @@ ends. See section 3.1.8.2.1 for an example of using a `UserTransaction` within a task. + +== Asynchronous Methods + +The `jakarta.enterprise.concurrent.Async` annotation annotates a +CDI managed bean method to run asynchronously or annotates a +CDI managed bean class with methods to run asynchronously. + +Each asynchronous method execution corresponds to a managed +`java.util.concurrent.CompletableFuture` instance that is backed by a +`jakarta.enterprise.concurrent.ManagedExecutorService` as its default +asynchronous execution facility. Its dependent stages +(and all dependent stages that are created from those, and so on) +continue to be backed by the managed executor service, +which also manages the propagation of context to completion stage actions. +Application code, including from within the asynchronous method, can query +the status of the completable future instance and can choose to complete +it at any time and by any means. + +==== Application Component Provider’s Responsibilities + +Application Component Providers (application developers) (EE2.11.2) +specify the `jakarta.enterprise.concurrent.Async` annotation on +CDI managed bean methods with return type of +`java.util.concurrent.CompletableFuture`, +`java.util.concurrent.CompletionStage`, or `void` to designate them for +asynchronous execution. Alternatively, the +`jakarta.enterprise.concurrent.Async` annotation can be specified on a +CDI managed bean class to designate all methods with return type of +`java.util.concurrent.CompletableFuture` or +`java.util.concurrent.CompletionStage` for asynchronous execution. + +The Application Component Provider supplies the implementation of the +asynchronous method. If the method has return type of +`java.util.concurrent.CompletableFuture` or +`java.util.concurrent.CompletionStage`, its implementation arranges for the +completion of a completable future or completion stage, which it returns +as the method result. The asynchronous method can create or obtain a new +completion stage for this purpose, or it can use the +`jakarta.enterprise.concurrent.Async.Result` API to obtain the same +instance that is being returned to the caller of the asynchronous method. + +===== Usage Example + +In this example, an application component wants to asynchronously identify +similar items that a customer might wish to purchase. + +====== Asynchronous Method Definition + +To check if the recommended similar items are currently available for +purchase, this asynchronous method relies on an external database that +is accessible via a resource reference that is defined in the application +component's java:comp namespace, which must be made available to the +asynchronous method. + +[source,java] +---- +public class ProductRecommendations { + @Async(executor = "java:module/env/concurrent/myExecutorRef") + public CompletableFuture> findSimilar(Cart cart, History h) { + Set combined = new LinkedHashSet(); + for (Item item : cart.items()) + combined.addAll(item.similar()); + for (Item item : h.recentlyViewed(3)) + combined.addAll(item.similar()); + combined.removeAll(cart.items()); + + try (Connection con = ((DataSource) InitialContext.doLookup( + "java:comp/env/jdbc/ds1")).getConnection()) { + PreparedStatement stmt = con.prepareStatement(CHECK_AVAILABILITY); + for (Item item : combined) { + ... Remove if the similar item is unavailable + } + } catch (NamingException | SQLException x) { + throw new CompletionException(x); + } + return Async.Result.complete(combined); + } +} +---- +====== Asynchronous Method Invocation + +The CDI managed bean with the asynchronous method is injected into a +a `Servlet`, which uses it to asynchronously determine the product +recommendations. + +[source,java] +---- +public class CheckoutServlet extends HttpServlet { + @Inject + ProductRecommendations recommendations; + + @Resource(name=”java:comp/env/jdbc/ds1”, lookup="jdbc/ds1") + DataSource ds; + + public void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException { + ... + recommendations.findSimilar(cust.getCart(), cust.getHistory()) + .thenAccept(recommended -> { + ... Update page with recommendations + }); + ... + } +} +---- + +==== Jakarta EE Product Provider’s Responsibilities + +The Jakarta EE Product Provider’s responsibilities are as defined in +EE.5.8.3. + +The Jakarta EE Product Provider registers a CDI interceptor +to arrange for the invocation of asynchronous methods on the +`jakarta.enterprise.concurrent.ManagedExecutorService` or +`jakarta.enterprise.concurrent.ManagedScheduledExecutorService` +that is specified by the `jakarta.enterprise.concurrent.Async` +annotation. + +The Jakarta EE Product Provider creates a +`java.util.concurrent.CompletableFuture` instance to associate with each +asynchronous method invocation, returning this same instance to the caller +of the asynchronous method, and providing it to the asynchronous method +implementation by means of the +`jakarta.enterprise.concurrent.Async.Result` API. The Jakarta EE Product +Provider completes this instance upon completion of the completion stage +that is returned by the asynchronous method implementation, which is a +no-op when the asynchronous method implementation chooses to return the +same instance. If the asynchronous method return type is `void` or if the +asynchronous method implementation raises an error or exception, the +Jakarta EE Product Provider completes this instance upon return from the +asynchronous method implementation. The Jakarta EE Product Provider +raises `java.util.concurrent.RejectedExecutionException` to the caller of +the asynchronous method if it cannot accept a method for asynchronous +execution, for example if supplied with an invalid JNDI name. +If the Jakarta EE Product Provider cannot start the asynchronous method +for any reason after this point, it completes the +`java.util.concurrent.CompletableFuture` instance with a +`java.util.concurrent.CancellationException`. + +==== Transaction Management + +Asynchronous method can be annotated with +`jakarta.transaction.Transactional` types of +`jakarta.transaction.Transactional.TxType.REQUIRES_NEW` or +`jakarta.transaction.Transactional.TxType.NOT_SUPPORTED`. + +When an asynchronous method is not annotated as +`jakarta.transaction.Transactional` or the transaction type is set to +`TxType.NOT_SUPPORTED`, the Jakarta EE Product Provider +must support user-managed global transaction demarcation using the +`jakarta.transaction.UserTransaction` interface, which is described in the +Jakarta Transactions specification. User-managed transactions allow +components to manually control global transaction demarcation +boundaries. Task implementations may optionally begin, commit, and +roll-back a transaction. See EE.4 for details on transaction management +in Jakarta EE. From f08d391e4e78c5e228d3ff9be154c61afc28e38b Mon Sep 17 00:00:00 2001 From: Nathan Rauh Date: Mon, 4 Oct 2021 17:01:33 -0500 Subject: [PATCH 2/6] Issue #43 code review comments to limit to methods and better document transactional --- .../jakarta/enterprise/concurrent/Async.java | 34 ++++++++----------- .../enterprise/concurrent/AsyncTest.java | 12 ------- .../main/asciidoc/jakarta-concurrency.adoc | 25 ++++++++------ 3 files changed, 29 insertions(+), 42 deletions(-) diff --git a/api/src/main/java/jakarta/enterprise/concurrent/Async.java b/api/src/main/java/jakarta/enterprise/concurrent/Async.java index 001c00e1..c4d1ac7a 100644 --- a/api/src/main/java/jakarta/enterprise/concurrent/Async.java +++ b/api/src/main/java/jakarta/enterprise/concurrent/Async.java @@ -28,8 +28,7 @@ import jakarta.interceptor.InterceptorBinding; /** - * Annotates a CDI managed bean method (or CDI managed bean class containing suitable methods) - * to run asynchronously. + * Annotates a CDI managed bean method to run asynchronously. *

* The Jakarta EE Product Provider runs the method on a {@link ManagedExecutorService} * and returns to the caller a {@link java.util.concurrent.CompletableFuture CompletableFuture} @@ -101,26 +100,21 @@ * * *

- * When annotating asynchronous methods at the method level, methods - * can have any of the following return types, with all other return types resulting in - * {@link java.lang.UnsupportedOperationException UnsupportedOperationException}: + * Methods with the following return types can be annotated to be + * asynchronous methods: *

    *
  • {@link java.util.concurrent.CompletableFuture CompletableFuture}
  • *
  • {@link java.util.concurrent.CompletionStage CompletionStage}
  • *
  • void
  • *
*

- * When annotating asynchronous methods at the class level, methods with - * any of the following return types are considered asynchronous methods, - * whereas methods with other return types are treated as normal - * (non-asynchronous) methods: - *

    - *
  • {@link java.util.concurrent.CompletableFuture CompletableFuture}
  • - *
  • {@link java.util.concurrent.CompletionStage CompletionStage}
  • - *
- *

- * If the Async annotation is present at both the method and class - * level, the annotation that is specified at the method level takes precedence. + * The Jakarta EE Product Provider raises + * {@link java.lang.UnsupportedOperationException UnsupportedOperationException} + * if other return types are used or if the annotation is placed at the class + * level. The injection target of ElementType.TYPE is to be used only + * by the CDI extension that is implemented by the Jakarta EE Product Provider to + * register the asynchronous method interceptor. Applications must only use the + * asynchronous method annotation at method level. *

* Exceptions that are raised by asynchronous methods are not raised directly * to the caller because the method runs asynchronously to the caller. @@ -152,9 +146,11 @@ * Interceptors with a lower priority, such as Transactional, must run on * the thread where the asynchronous method executes, rather than on the submitting thread. * When an asynchronous method is annotated as Transactional, - * the transactional types TxType.REQUIRES_NEW and - * TxType.NOT_SUPPORTED can be used. All other transaction attributes must - * result in {@link java.lang.UnsupportedOperationException UnsupportedOperationException} + * the transactional types which can be used are: + * TxType.REQUIRES_NEW, which causes the method to run in a new transaction, and + * TxType.NOT_SUPPORTED, which causes the method to run with no transaction. + * All other transaction attributes must result in + * {@link java.lang.UnsupportedOperationException UnsupportedOperationException} * upon invocation of the asynchronous method. * * @since 3.0 diff --git a/api/src/test/java/jakarta/enterprise/concurrent/AsyncTest.java b/api/src/test/java/jakarta/enterprise/concurrent/AsyncTest.java index 143d3b15..764d8a5c 100644 --- a/api/src/test/java/jakarta/enterprise/concurrent/AsyncTest.java +++ b/api/src/test/java/jakarta/enterprise/concurrent/AsyncTest.java @@ -43,7 +43,6 @@ * for asychronous methods, verifying that they compile. * The Async.Result class is also tested. */ -@Async(executor = "java:module/env/concurrent/class-level-async-executor") public class AsyncTest { /** * Example from the section on Asynchronous Methods in the Concurrency spec. @@ -195,17 +194,6 @@ List flightsTo() { } } - /** - * Verify that the Async annotation can be configured at class level - * and that its executor field is populated with the specified value. - */ - @Test - public void testAsyncClassLevelAnnotation() throws Exception { - Async anno = AsyncTest.class.getAnnotation(Async.class); - assertNotNull(anno); - assertEquals("java:module/env/concurrent/class-level-async-executor", anno.executor()); - } - /** * Verify that the first usage example in the Async class JavaDoc compiles. */ diff --git a/specification/src/main/asciidoc/jakarta-concurrency.adoc b/specification/src/main/asciidoc/jakarta-concurrency.adoc index 196aa3a0..57d843e7 100644 --- a/specification/src/main/asciidoc/jakarta-concurrency.adoc +++ b/specification/src/main/asciidoc/jakarta-concurrency.adoc @@ -2306,8 +2306,7 @@ task. == Asynchronous Methods The `jakarta.enterprise.concurrent.Async` annotation annotates a -CDI managed bean method to run asynchronously or annotates a -CDI managed bean class with methods to run asynchronously. +CDI managed bean method to run asynchronously. Each asynchronous method execution corresponds to a managed `java.util.concurrent.CompletableFuture` instance that is backed by a @@ -2327,11 +2326,7 @@ specify the `jakarta.enterprise.concurrent.Async` annotation on CDI managed bean methods with return type of `java.util.concurrent.CompletableFuture`, `java.util.concurrent.CompletionStage`, or `void` to designate them for -asynchronous execution. Alternatively, the -`jakarta.enterprise.concurrent.Async` annotation can be specified on a -CDI managed bean class to designate all methods with return type of -`java.util.concurrent.CompletableFuture` or -`java.util.concurrent.CompletionStage` for asynchronous execution. +asynchronous execution. The Application Component Provider supplies the implementation of the asynchronous method. If the method has return type of @@ -2443,10 +2438,18 @@ for any reason after this point, it completes the ==== Transaction Management -Asynchronous method can be annotated with -`jakarta.transaction.Transactional` types of -`jakarta.transaction.Transactional.TxType.REQUIRES_NEW` or -`jakarta.transaction.Transactional.TxType.NOT_SUPPORTED`. +When an asynchronous method is also annotated with +`jakarta.transaction.Transactional`, the transactional types which can be +used are: + +* `jakarta.transaction.Transactional.TxType.REQUIRES_NEW` - +which causes the method to run in a new transaction +* `jakarta.transaction.Transactional.TxType.NOT_SUPPORTED` - +which causes the method to run with no transaction + +All other transaction attributes must result in +`java.lang.UnsupportedOperationException` upon invocation of the +asynchronous method. When an asynchronous method is not annotated as `jakarta.transaction.Transactional` or the transaction type is set to From 4e3d0b67397e160c23ecd542042b9ac83dc6f6c2 Mon Sep 17 00:00:00 2001 From: Nathan Rauh Date: Wed, 6 Oct 2021 09:06:33 -0500 Subject: [PATCH 3/6] Issue #43 code review comment to clarify restriction against usage from EJB --- api/src/main/java/jakarta/enterprise/concurrent/Async.java | 3 +++ specification/src/main/asciidoc/jakarta-concurrency.adoc | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/api/src/main/java/jakarta/enterprise/concurrent/Async.java b/api/src/main/java/jakarta/enterprise/concurrent/Async.java index c4d1ac7a..692e80b0 100644 --- a/api/src/main/java/jakarta/enterprise/concurrent/Async.java +++ b/api/src/main/java/jakarta/enterprise/concurrent/Async.java @@ -29,6 +29,9 @@ /** * Annotates a CDI managed bean method to run asynchronously. + * The CDI managed bean must not be an EJB or JSF managed bean, + * and neither the method nor its class can be annotated with + * the MicroProfile Asynchronous annotation. *

* The Jakarta EE Product Provider runs the method on a {@link ManagedExecutorService} * and returns to the caller a {@link java.util.concurrent.CompletableFuture CompletableFuture} diff --git a/specification/src/main/asciidoc/jakarta-concurrency.adoc b/specification/src/main/asciidoc/jakarta-concurrency.adoc index 57d843e7..5a7e2bb0 100644 --- a/specification/src/main/asciidoc/jakarta-concurrency.adoc +++ b/specification/src/main/asciidoc/jakarta-concurrency.adoc @@ -2306,7 +2306,9 @@ task. == Asynchronous Methods The `jakarta.enterprise.concurrent.Async` annotation annotates a -CDI managed bean method to run asynchronously. +CDI managed bean method to run asynchronously. The CDI managed bean must +not be an EJB or JSF managed bean, and neither the method nor its class +can be annotated with the MicroProfile Asynchronous annotation. Each asynchronous method execution corresponds to a managed `java.util.concurrent.CompletableFuture` instance that is backed by a From d027c29ea914943f0722b6047d618526a6458806 Mon Sep 17 00:00:00 2001 From: Nathan Rauh Date: Fri, 8 Oct 2021 11:42:22 -0500 Subject: [PATCH 4/6] Issue #43 address checkstyle issues --- .../jakarta/enterprise/concurrent/Async.java | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/api/src/main/java/jakarta/enterprise/concurrent/Async.java b/api/src/main/java/jakarta/enterprise/concurrent/Async.java index 692e80b0..050c082b 100644 --- a/api/src/main/java/jakarta/enterprise/concurrent/Async.java +++ b/api/src/main/java/jakarta/enterprise/concurrent/Async.java @@ -233,7 +233,7 @@ * @since 3.0 */ public static class Result { - private static final ThreadLocal> futures = new ThreadLocal>(); + private static final ThreadLocal> FUTURES = new ThreadLocal>(); /** * Completes the {@link java.util.concurrent.CompletableFuture CompletableFuture} @@ -248,11 +248,12 @@ public static class Result { * @throws IllegalStateException if the CompletableFuture for an asynchronous * method is not present on the thread. */ - public static CompletableFuture complete(T result) { + public static CompletableFuture complete(final T result) { @SuppressWarnings("unchecked") - CompletableFuture future = (CompletableFuture) futures.get(); - if (future == null) + CompletableFuture future = (CompletableFuture) FUTURES.get(); + if (future == null) { throw new IllegalStateException(); + } future.complete(result); return future; } @@ -271,9 +272,10 @@ public static CompletableFuture complete(T result) { */ public static CompletableFuture getFuture() { @SuppressWarnings("unchecked") - CompletableFuture future = (CompletableFuture) futures.get(); - if (future == null) + CompletableFuture future = (CompletableFuture) FUTURES.get(); + if (future == null) { throw new IllegalStateException(); + } return future; } @@ -293,11 +295,12 @@ public static CompletableFuture getFuture() { * @param future CompletableFuture that the container returns to the caller, * or null to clear it. */ - public static void setFuture(CompletableFuture future) { - if (future == null) - futures.remove(); - else - futures.set(future); + public static void setFuture(final CompletableFuture future) { + if (future == null) { + FUTURES.remove(); + } else { + FUTURES.set(future); + } } } -} \ No newline at end of file +} From 47810acc64b940eba235cabf201f0d467888f082 Mon Sep 17 00:00:00 2001 From: Nathan Rauh Date: Thu, 14 Oct 2021 09:08:21 -0500 Subject: [PATCH 5/6] Issue #43 code review found Java EE terms in doc --- api/src/main/java/jakarta/enterprise/concurrent/Async.java | 2 +- specification/src/main/asciidoc/jakarta-concurrency.adoc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/main/java/jakarta/enterprise/concurrent/Async.java b/api/src/main/java/jakarta/enterprise/concurrent/Async.java index 050c082b..1b658bb3 100644 --- a/api/src/main/java/jakarta/enterprise/concurrent/Async.java +++ b/api/src/main/java/jakarta/enterprise/concurrent/Async.java @@ -29,7 +29,7 @@ /** * Annotates a CDI managed bean method to run asynchronously. - * The CDI managed bean must not be an EJB or JSF managed bean, + * The CDI managed bean must not be a Jakarta Enterprise Bean, * and neither the method nor its class can be annotated with * the MicroProfile Asynchronous annotation. *

diff --git a/specification/src/main/asciidoc/jakarta-concurrency.adoc b/specification/src/main/asciidoc/jakarta-concurrency.adoc index 5a7e2bb0..9e08ac7d 100644 --- a/specification/src/main/asciidoc/jakarta-concurrency.adoc +++ b/specification/src/main/asciidoc/jakarta-concurrency.adoc @@ -2307,7 +2307,7 @@ task. The `jakarta.enterprise.concurrent.Async` annotation annotates a CDI managed bean method to run asynchronously. The CDI managed bean must -not be an EJB or JSF managed bean, and neither the method nor its class +not be a Jakarta Enterprise Bean, and neither the method nor its class can be annotated with the MicroProfile Asynchronous annotation. Each asynchronous method execution corresponds to a managed From d1f281e2dbdd71d51bdc31845e84e310b272adb7 Mon Sep 17 00:00:00 2001 From: Nathan Rauh Date: Thu, 14 Oct 2021 14:40:17 -0500 Subject: [PATCH 6/6] Issue #43 rename to Asynchronous per outcome of vote --- .../{Async.java => Asynchronous.java} | 16 ++-- .../{AsyncTest.java => AsynchronousTest.java} | 89 ++++++++++--------- .../main/asciidoc/jakarta-concurrency.adoc | 14 +-- 3 files changed, 60 insertions(+), 59 deletions(-) rename api/src/main/java/jakarta/enterprise/concurrent/{Async.java => Asynchronous.java} (97%) rename api/src/test/java/jakarta/enterprise/concurrent/{AsyncTest.java => AsynchronousTest.java} (78%) diff --git a/api/src/main/java/jakarta/enterprise/concurrent/Async.java b/api/src/main/java/jakarta/enterprise/concurrent/Asynchronous.java similarity index 97% rename from api/src/main/java/jakarta/enterprise/concurrent/Async.java rename to api/src/main/java/jakarta/enterprise/concurrent/Asynchronous.java index 1b658bb3..e5d0721b 100644 --- a/api/src/main/java/jakarta/enterprise/concurrent/Async.java +++ b/api/src/main/java/jakarta/enterprise/concurrent/Asynchronous.java @@ -42,19 +42,19 @@ * as defined by the ManagedExecutorService JavaDoc API. * The Jakarta EE Product Provider makes this CompletableFuture available * to the asynchronous method implementation via the - * {@link Result#getFuture Async.Result.getFuture} and - * {@link Result#complete Async.Result.complete} methods. + * {@link Result#getFuture Asynchronous.Result.getFuture} and + * {@link Result#complete Asynchronous.Result.complete} methods. *

* For example, * *

- * {@literal @}Async
+ * {@literal @}Asynchronous
  * public CompletableFuture{@literal } hoursWorked(LocalDate from, LocalDate to) {
  *     // Application component's context is made available to the async method,
  *     try (Connection con = ((DataSource) InitialContext.doLookup(
  *         "java:comp/env/jdbc/timesheetDB")).getConnection()) {
  *         ...
- *         return Async.Result.complete(total);
+ *         return Asynchronous.Result.complete(total);
  *     } catch (NamingException | SQLException x) {
  *         throw new CompletionException(x);
  *     }
@@ -81,7 +81,7 @@
  * For example,
  *
  * 
- * {@literal @}Async
+ * {@literal @}Asynchronous
  * public CompletableFuture{@literal >} findSingleLayoverFlights(Location source, Location dest) {
  *     try {
  *         ManagedExecutorService executor = InitialContext.doLookup(
@@ -165,7 +165,7 @@
 @InterceptorBinding
 @Retention(RetentionPolicy.RUNTIME)
 @Target({ ElementType.METHOD, ElementType.TYPE })
-public @interface Async {
+public @interface Asynchronous {
     /**
      * JNDI name of a {@link ManagedExecutorService} or {@link ManagedScheduledExecutorService}
      * upon which to run the asynchronous method.
@@ -207,9 +207,9 @@
      * For example,
      *
      * 
-     * {@literal @}Async
+     * {@literal @}Asynchronous
      * public CompletableFuture{@literal } hoursWorked(LocalDateTime from, LocalDateTime to) {
-     *     CompletableFuture{@literal } future = Async.Result.getFuture();
+     *     CompletableFuture{@literal } future = Asynchronous.Result.getFuture();
      *     if (future.isDone())
      *         return future;
      *
diff --git a/api/src/test/java/jakarta/enterprise/concurrent/AsyncTest.java b/api/src/test/java/jakarta/enterprise/concurrent/AsynchronousTest.java
similarity index 78%
rename from api/src/test/java/jakarta/enterprise/concurrent/AsyncTest.java
rename to api/src/test/java/jakarta/enterprise/concurrent/AsynchronousTest.java
index 764d8a5c..6a842cd3 100644
--- a/api/src/test/java/jakarta/enterprise/concurrent/AsyncTest.java
+++ b/api/src/test/java/jakarta/enterprise/concurrent/AsynchronousTest.java
@@ -41,13 +41,13 @@
 /**
  * This test includes the code examples from the specification and JavaDoc
  * for asychronous methods, verifying that they compile.
- * The Async.Result class is also tested.
+ * The Asynchronous.Result class is also tested.
  */
-public class AsyncTest {
+public class AsynchronousTest {
     /**
      * Example from the section on Asynchronous Methods in the Concurrency spec.
      */
-    @Async(executor = "java:module/env/concurrent/myExecutorRef")
+    @Asynchronous(executor = "java:module/env/concurrent/myExecutorRef")
     public CompletableFuture> findSimilar(Cart cart, History h) {
         Set combined = new LinkedHashSet();
         for (Item item : cart.items())
@@ -65,13 +65,13 @@ public CompletableFuture> findSimilar(Cart cart, History h) {
         } catch (NamingException | SQLException x) {
             throw new CompletionException(x);
         }
-        return Async.Result.complete(combined);
+        return Asynchronous.Result.complete(combined);
     }
 
     /**
-     * Second example from the Async class JavaDoc.
+     * Second example from the Asynchronous class JavaDoc.
      */
-    @Async
+    @Asynchronous
     public CompletableFuture> findSingleLayoverFlights(Location source, Location dest) {
         try {
             ManagedExecutorService executor = InitialContext.doLookup(
@@ -86,27 +86,27 @@ public CompletableFuture> findSingleLayoverFlights(Location sour
     }
 
     /**
-     * First example from the Async class JavaDoc.
+     * First example from the Asynchronous class JavaDoc.
      */
-    @Async
+    @Asynchronous
     public CompletableFuture hoursWorked(LocalDate from, LocalDate to) {
         // Application component's context is made available to the async method,
         try (Connection con = ((DataSource) InitialContext.doLookup(
             "java:comp/env/jdbc/timesheetDB")).getConnection()) {
             // ...
             double total = 40.0; // added to make the example compile
-            return Async.Result.complete(total);
+            return Asynchronous.Result.complete(total);
         } catch (NamingException | SQLException x) {
             throw new CompletionException(x);
         }
     }
 
     /**
-     * Example from the Async.Result class JavaDoc.
+     * Example from the Asynchronous.Result class JavaDoc.
      */
-    @Async
+    @Asynchronous
     public CompletableFuture hoursWorked(LocalDateTime from, LocalDateTime to) {
-        CompletableFuture future = Async.Result.getFuture();
+        CompletableFuture future = Asynchronous.Result.getFuture();
         if (future.isDone())
             return future;
 
@@ -169,7 +169,7 @@ public Set similar() {
     }
 
     /**
-     * Used by example from Async class JavaDoc.
+     * Used by example from Asynchronous class JavaDoc.
      * This code doesn't do anything; we only need the method signatures to verify compilation of the example.
      */
     static class Itinerary {
@@ -182,7 +182,7 @@ static List sortByPrice(List list) {
     }
 
     /**
-     * Used by example from Async class JavaDoc.
+     * Used by example from Asynchronous class JavaDoc.
      * This code doesn't do anything; we only need the method signatures to verify compilation of the example.
      */
     static class Location {
@@ -195,17 +195,17 @@ List flightsTo() {
     }
 
     /**
-     * Verify that the first usage example in the Async class JavaDoc compiles.
+     * Verify that the first usage example in the Asynchronous class JavaDoc compiles.
      */
     @Test
-    public void testAsyncJavaDocUsageExample1() {
+    public void testAsynchronousJavaDocUsageExample1() {
         LocalDateTime mon = LocalDateTime.of(2021, 9, 13, 0, 0, 0);
         LocalDateTime fri = LocalDateTime.of(2021, 9, 17, 23, 59, 59);
 
         // Normally, the Jakarta EE Product Provider would do this on the same
         // thread where the asynchronous method executes, just before it starts.
         CompletableFuture future = new CompletableFuture();
-        Async.Result.setFuture(future);
+        Asynchronous.Result.setFuture(future);
 
         // There is no interceptor when running these tests, so this won't actually run async.
         try {
@@ -221,7 +221,7 @@ public void testAsyncJavaDocUsageExample1() {
         } finally {
             // Normally, the Jakarta EE Product Provider would do this on the same
             // thread where the asynchronous method executes, just after it ends.
-            Async.Result.setFuture(null);
+            Asynchronous.Result.setFuture(null);
         }
 
         // Naming lookup will have failed,
@@ -229,10 +229,10 @@ public void testAsyncJavaDocUsageExample1() {
     }
 
     /**
-     * Verify that the second usage example in the Async class JavaDoc compiles.
+     * Verify that the second usage example in the Asynchronous class JavaDoc compiles.
      */
     @Test
-    public void testAsyncJavaDocUsageExample2() {
+    public void testAsynchronousJavaDocUsageExample2() {
         Location RST = new Location();
         Location DEN = new Location();
 
@@ -245,65 +245,66 @@ public void testAsyncJavaDocUsageExample2() {
     }
 
     /**
-     * Verify that the Async annotation can be configured at method level
+     * Verify that the Asynchronous annotation can be configured at method level
      * and that its executor field defaults to the JNDI name of the
      * built-in ManagedExecutorService that is provided by the Jakarta EE platform.
      */
     @Test
-    public void testAsyncMethodLevelAnnotation() throws Exception {
-        Async anno = AsyncTest.class.getMethod("hoursWorked", LocalDate.class, LocalDate.class)
-                                    .getAnnotation(Async.class);
+    public void testAsynchronousMethodLevelAnnotation() throws Exception {
+        Asynchronous anno = AsynchronousTest.class
+                .getMethod("hoursWorked", LocalDate.class, LocalDate.class)
+                .getAnnotation(Asynchronous.class);
         assertNotNull(anno);
         assertEquals("java:comp/DefaultManagedExecutorService", anno.executor());
     }
 
     /**
-     * Invoke all of the static methods of Async.Result.
+     * Invoke all of the static methods of Asynchronous.Result.
      */
     @Test
-    public void testAsyncResult() {
+    public void testAsynchronousResult() {
         try {
-            fail("Must not be present by default: " + Async.Result.getFuture());
+            fail("Must not be present by default: " + Asynchronous.Result.getFuture());
         } catch (IllegalStateException x) {
             // Pass - detected that this was not invoked from an async method
         }
 
         CompletableFuture stringFuture = new CompletableFuture();
-        Async.Result.setFuture(stringFuture);
-        assertEquals(stringFuture, Async.Result.getFuture());
-        Async.Result.setFuture(null);
+        Asynchronous.Result.setFuture(stringFuture);
+        assertEquals(stringFuture, Asynchronous.Result.getFuture());
+        Asynchronous.Result.setFuture(null);
         try {
-            fail("Must not be present after removing: " + Async.Result.getFuture());
+            fail("Must not be present after removing: " + Asynchronous.Result.getFuture());
         } catch (IllegalStateException x) {
             // Pass - detected that this was not invoked from an async method
         }
 
         CompletableFuture intFuture = new CompletableFuture();
-        Async.Result.setFuture(intFuture);
-        assertEquals(intFuture, Async.Result.complete(100));
-        Async.Result.setFuture(null);
+        Asynchronous.Result.setFuture(intFuture);
+        assertEquals(intFuture, Asynchronous.Result.complete(100));
+        Asynchronous.Result.setFuture(null);
         try {
-            fail("Must not be present after removing: " + Async.Result.getFuture());
+            fail("Must not be present after removing: " + Asynchronous.Result.getFuture());
         } catch (IllegalStateException x) {
             // Pass - detected that this was not invoked from an async method
         }
 
         CompletableFuture booleanFuture = new CompletableFuture();
-        Async.Result.setFuture(booleanFuture);
-        assertEquals(booleanFuture, Async.Result.getFuture());
-        assertEquals(booleanFuture, Async.Result.complete(true));
-        Async.Result.setFuture(null);
+        Asynchronous.Result.setFuture(booleanFuture);
+        assertEquals(booleanFuture, Asynchronous.Result.getFuture());
+        assertEquals(booleanFuture, Asynchronous.Result.complete(true));
+        Asynchronous.Result.setFuture(null);
         try {
-            fail("Must not be present after removing: " + Async.Result.getFuture());
+            fail("Must not be present after removing: " + Asynchronous.Result.getFuture());
         } catch (IllegalStateException x) {
             // Pass - detected that this was not invoked from an async method
         }
 
         CompletableFuture unusedFuture = new CompletableFuture();
-        Async.Result.setFuture(unusedFuture);
-        Async.Result.setFuture(null);
+        Asynchronous.Result.setFuture(unusedFuture);
+        Asynchronous.Result.setFuture(null);
         try {
-            fail("Must not be present after removing: " + Async.Result.getFuture());
+            fail("Must not be present after removing: " + Asynchronous.Result.getFuture());
         } catch (IllegalStateException x) {
             // Pass - detected that this was not invoked from an async method
         }
@@ -314,7 +315,7 @@ public void testAsyncResult() {
      * Concurrency spec compiles.
      */
     @Test
-    public void testAsyncUsageExampleFromSpec() throws Exception {
+    public void testAsynchronousUsageExampleFromSpec() throws Exception {
         Customer cust = new Customer();
 
         try {
diff --git a/specification/src/main/asciidoc/jakarta-concurrency.adoc b/specification/src/main/asciidoc/jakarta-concurrency.adoc
index 9e08ac7d..889814eb 100644
--- a/specification/src/main/asciidoc/jakarta-concurrency.adoc
+++ b/specification/src/main/asciidoc/jakarta-concurrency.adoc
@@ -2305,7 +2305,7 @@ task.
 
 == Asynchronous Methods
 
-The `jakarta.enterprise.concurrent.Async` annotation annotates a
+The `jakarta.enterprise.concurrent.Asynchronous` annotation annotates a
 CDI managed bean method to run asynchronously. The CDI managed bean must
 not be a Jakarta Enterprise Bean, and neither the method nor its class
 can be annotated with the MicroProfile Asynchronous annotation.
@@ -2324,7 +2324,7 @@ it at any time and by any means.
 ==== Application Component Provider’s Responsibilities
 
 Application Component Providers (application developers) (EE2.11.2)
-specify the `jakarta.enterprise.concurrent.Async` annotation on
+specify the `jakarta.enterprise.concurrent.Asynchronous` annotation on
 CDI managed bean methods with return type of
 `java.util.concurrent.CompletableFuture`,
 `java.util.concurrent.CompletionStage`, or `void` to designate them for
@@ -2337,7 +2337,7 @@ asynchronous method. If the method has return type of
 completion of a completable future or completion stage, which it returns
 as the method result. The asynchronous method can create or obtain a new
 completion stage for this purpose, or it can use the
-`jakarta.enterprise.concurrent.Async.Result` API to obtain the same
+`jakarta.enterprise.concurrent.Asynchronous.Result` API to obtain the same
 instance that is being returned to the caller of the asynchronous method.
 
 ===== Usage Example
@@ -2356,7 +2356,7 @@ asynchronous method.
 [source,java]
 ----
 public class ProductRecommendations {
-   @Async(executor = "java:module/env/concurrent/myExecutorRef")
+   @Asynchronous(executor = "java:module/env/concurrent/myExecutorRef")
    public CompletableFuture> findSimilar(Cart cart, History h) {
       Set combined = new LinkedHashSet();
       for (Item item : cart.items())
@@ -2374,7 +2374,7 @@ public class ProductRecommendations {
       } catch (NamingException | SQLException x) {
          throw new CompletionException(x);
       }
-      return Async.Result.complete(combined);
+      return Asynchronous.Result.complete(combined);
    }
 }
 ----
@@ -2414,7 +2414,7 @@ The Jakarta EE Product Provider registers a CDI interceptor
 to arrange for the invocation of asynchronous methods on the
 `jakarta.enterprise.concurrent.ManagedExecutorService` or
 `jakarta.enterprise.concurrent.ManagedScheduledExecutorService`
-that is specified by the `jakarta.enterprise.concurrent.Async`
+that is specified by the `jakarta.enterprise.concurrent.Asynchronous`
 annotation.
 
 The Jakarta EE Product Provider creates a
@@ -2422,7 +2422,7 @@ The Jakarta EE Product Provider creates a
 asynchronous method invocation, returning this same instance to the caller
 of the asynchronous method, and providing it to the asynchronous method
 implementation by means of the
-`jakarta.enterprise.concurrent.Async.Result` API. The Jakarta EE Product
+`jakarta.enterprise.concurrent.Asynchronous.Result` API. The Jakarta EE Product
 Provider completes this instance upon completion of the completion stage
 that is returned by the asynchronous method implementation, which is a
 no-op when the asynchronous method implementation chooses to return the