Using Asynchronous Method Invocation in Session Beans

We are working on a fresh, updated Jakarta EE Tutorial. This section hasn’t yet been updated.

This chapter discusses how to implement asynchronous business methods in session beans and call them from enterprise bean clients.

Asynchronous Method Invocation

Session beans can implement asynchronous methods, business methods where control is returned to the client by the enterprise bean container before the method is invoked on the session bean instance. Clients may then use the Java SE concurrency API to retrieve the result, cancel the invocation, and check for exceptions. Asynchronous methods are typically used for long-running operations, for processor-intensive tasks, for background tasks, to increase application throughput, or to improve application response time if the method invocation result isn’t required immediately.

When a session bean client invokes a typical non-asynchronous business method, control is not returned to the client until the method has completed. Clients calling asynchronous methods, however, immediately have control returned to them by the enterprise bean container. This allows the client to perform other tasks while the method invocation completes. If the method returns a result, the result is an implementation of the java.util.concurrent.Future<V> interface, where "V" is the result value type. The Future<V> interface defines methods the client may use to check whether the computation is completed, wait for the invocation to complete, retrieve the final result, and cancel the invocation.

Creating an Asynchronous Business Method

Annotate a business method with jakarta.ejb.Asynchronous to mark that method as an asynchronous method, or apply @Asynchronous at the class level to mark all the business methods of the session bean as asynchronous methods. Session bean methods that expose web services can’t be asynchronous.

Asynchronous methods must return either void or an implementation of the Future<V> interface. Asynchronous methods that return void can’t declare application exceptions, but if they return Future<V>, they may declare application exceptions. For example:

@Asynchronous
public Future<String> processPayment(Order order) throws PaymentException { ... }

This method will attempt to process the payment of an order, and return the status as a String. Even if the payment processor takes a long time, the client can continue working, and display the result when the processing finally completes.

The jakarta.ejb.AsyncResult<V> class is a concrete implementation of the Future<V> interface provided as a helper class for returning asynchronous results. AsyncResult has a constructor with the result as a parameter, making it easy to create Future<V> implementations. For example, the processPayment method would use AsyncResult to return the status as a String:

@Asynchronous
public Future<String> processPayment(Order order) throws PaymentException {
    ...
    String status = ...;
    return new AsyncResult<String>(status);
}

The result is returned to the enterprise bean container, not directly to the client, and the enterprise bean container makes the result available to the client. The session bean can check whether the client requested that the invocation be cancelled by calling the jakarta.ejb.SessionContext.wasCancelled method. For example:

@Asynchronous
public Future<String> processPayment(Order order) throws PaymentException {
    ...
    if (SessionContext.wasCancelled()) {
        // clean up
    } else {
        // process the payment
    }
    ...
}

Calling Asynchronous Methods from Enterprise Bean Clients

Session bean clients call asynchronous methods just like non-asynchronous business methods. If the asynchronous method returns a result, the client receives a Future<V> instance as soon as the method is invoked. This instance can be used to retrieve the final result, cancel the invocation, check whether the invocation has completed, check whether any exceptions were thrown during processing, and check whether the invocation was cancelled.

Retrieving the Final Result from an Asynchronous Method Invocation

The client may retrieve the result using one of the Future<V>.get methods. If processing hasn’t been completed by the session bean handling the invocation, calling one of the get methods will result in the client halting execution until the invocation completes. Use the Future<V>.isDone method to determine whether processing has completed before calling one of the get methods.

The get() method returns the result as the type specified in the type value of the Future<V> instance. For example, calling Future<String>.get() will return a String object. If the method invocation was cancelled, calls to get() result in a java.util.concurrent.CancellationException being thrown. If the invocation resulted in an exception during processing by the session bean, calls to get() result in a java.util.concurrent.ExecutionException being thrown. The cause of the ExecutionException may be retrieved by calling the ExecutionException.getCause method.

The get(long timeout, java.util.concurrent.TimeUnit unit) method is similar to the get() method, but allows the client to set a timeout value. If the timeout value is exceeded, a java.util.concurrent.TimeoutException is thrown. See the Javadoc for the TimeUnit class for the available units of time to specify the timeout value.

Cancelling an Asynchronous Method Invocation

Call the cancel(boolean mayInterruptIfRunning) method on the Future<V> instance to attempt to cancel the method invocation. The cancel method returns true if the cancellation was successful and false if the method invocation cannot be cancelled.

When the invocation cannot be cancelled, the mayInterruptIfRunning parameter is used to alert the session bean instance on which the method invocation is running that the client attempted to cancel the invocation. If mayInterruptIfRunning is set to true, calls to SessionContext.wasCancelled by the session bean instance will return true. If mayInterruptIfRunning is to set false, calls to SessionContext.wasCancelled by the session bean instance will return false.

The Future<V>.isCancelled method is used to check whether the method invocation was cancelled before the asynchronous method invocation completed by calling Future<V>.cancel. The isCancelled method returns true if the invocation was cancelled.

Checking the Status of an Asynchronous Method Invocation

The Future<V>.isDone method returns true if the session bean instance completed processing the method invocation. The isDone method returns true if the asynchronous method invocation completed normally, was cancelled, or resulted in an exception. That is, isDone indicates only whether the session bean has completed processing the invocation.

The async Example Application

The async example demonstrates how to define an asynchronous business method on a session bean and call it from a web client. This example contains two modules.

  • A web application (async-war) that contains a stateless session bean and a Jakarta Faces interface. The MailerBean stateless session bean defines an asynchronous method, sendMessage, which uses the Jakarta Mail API to send an email to an specified email address.

  • An auxiliary Java SE program (async-smtpd) that simulates an SMTP server. This program listens on TCP port 3025 for SMTP requests and prints the email messages to the standard output (instead of delivering them).

The following section describes the architecture of the async-war module.

Architecture of the async-war Module

The async-war module consists of a single stateless session bean, MailerBean, and a Jakarta Faces web application front end that uses Facelets tags in XHTML files to display a form for users to enter the email address for the recipient of an email. The status of the email is updated when the email is finally sent.

The MailerBean session bean injects a Jakarta Mail resource used to send an email message to an address specified by the user. The message is created, modified, and sent using the Jakarta Mail API. The session bean looks like this:

@Named
@Stateless
public class MailerBean {
    @Resource(name="mail/myExampleSession")
    private Session session;
    private static final Logger logger =
            Logger.getLogger(MailerBean.class.getName());

    @Asynchronous
    public Future<String> sendMessage(String email) {
        String status;
        try {
            Properties properties = new Properties();
            properties.put("mail.smtp.port", "3025");
            session = Session.getInstance(properties);
            Message message = new MimeMessage(session);
            message.setFrom();
            message.setRecipients(Message.RecipientType.TO,
                    InternetAddress.parse(email, false));
            message.setSubject("Test message from async example");
            message.setHeader("X-Mailer", "Jakarta Mail");
            DateFormat dateFormatter = DateFormat
                    .getDateTimeInstance(DateFormat.LONG, DateFormat.SHORT);
            Date timeStamp = new Date();
            String messageBody = "This is a test message from the async "
                    + "example of the Jakarta EE Tutorial. It was sent on "
                    + dateFormatter.format(timeStamp)
                    + ".";
            message.setText(messageBody);
            message.setSentDate(timeStamp);
            Transport.send(message);
            status = "Sent";
            logger.log(Level.INFO, "Mail sent to {0}", email);
        } catch (MessagingException ex) {
            logger.severe("Error in sending message.");
            status = "Encountered an error: " + ex.getMessage();
            logger.severe(ex.getMessage());
        }
        return new AsyncResult<>(status);
    }
}

The injected Jakarta Mail resource can be configured through the GlassFish Server Administration Console, through a GlassFish Server administrative command, or through a resource configuration file packaged with the application. The resource configuration can be modified at runtime by the GlassFish Server administrator to use a different mail server or transport protocol.

The web client consists of a Facelets template, template.xhtml; two Facelets clients, index.xhtml and response.xhtml. The index.xhtml file contains a form for the target email address. When the user submits the form, the method is called that uses an injected instance of the MailerBean session bean to call MailerBean.sendMessage. The result is sent to the response.xhtml Facelets view.

Running the async Example

You can use either NetBeans IDE or Maven to build, package, deploy, and run the async example.

To Run the async Example Application Using NetBeans IDE

  1. Make sure that GlassFish Server has been started (see Starting and Stopping GlassFish Server).

  2. From the File menu, choose Open Project.

  3. In the Open Project dialog box, navigate to:

    jakartaee-examples/tutorial/ejb
  4. Select the async folder, select Open Required Projects, and click Open Project.

  5. In the Projects tab, right-click the async-smtpd project and select Run.

    The SMTP server simulator starts accepting connections. The async-smptd output tab shows the following message:

    [Test SMTP server listening on port 3025]
  6. In the Projects tab, right-click the async-war project and select Build.

    This command configures the Jakarta Mail resource using a GlassFish Server administrative command and builds, packages, and deploys the async-war module.

  7. Open the following URL in a web browser window:

    http://localhost:8080/async-war
  8. In the web browser window, enter an email address and click Send email.

    The MailerBean stateless bean uses the Jakarta Mail API to deliver an email to the SMTP server simulator. The async-smptd output window in NetBeans IDE shows the resulting email message, including its headers.

  9. To stop the SMTP server simulator, click the X button on the right side of the status bar in NetBeans IDE.

  10. Delete the Jakarta Mail session resource.

    1. In the Services tab, expand the Servers node, then expand the GlassFish Server server node.

    2. Expand the Resources node, then expand the Jakarta Mail Sessions node.

    3. Right-click mail/myExampleSession and select Unregister.

To Run the async Example Application Using Maven

  1. Make sure that GlassFish Server has been started (see Starting and Stopping GlassFish Server).

  2. In a terminal window, go to:

    jakartaee-examples/tutorial/ejb/async/async-smtpd/
  3. Enter the following command to build and package the SMTP server simulator:

    mvn install
  4. Enter the following command to start the STMP server simulator:

    mvn exec:java

    The following message appears:

    [Test SMTP server listening on port 3025]

    Keep this terminal window open.

  5. In a new terminal window, go to:

    jakartaee-examples/tutorial/ejb/async/async-war
  6. Enter the following command to configure the Jakarta Mail resource and to build, package, and deploy the async-war module:

    mvn install
  7. Open the following URL in a web browser window:

    http://localhost:8080/async-war
  8. In the web browser window, enter an email address and click Send email.

    The MailerBean stateless bean uses the Jakarta Mail API to deliver an email to the SMTP server simulator. The resulting email message appears on the first terminal window, including its headers.

  9. To stop the SMTP server simulator, close the terminal window in which you issued the command to start the STMP server simulator.

  10. To delete the Jakarta Mail session resource, type the following command:

    asadmin delete-mail-resource mail/myExampleSession