Asynchronous Programming with the AWS SDK for Java

This article describes the various synchronous and asynchronous approaches for making requests with the AWS SDK for Java and concludes with some additional information to help you use the asynchronous features successfully.


Submitted By: Jason@AWS
AWS Products Used: AWS SDK for Java
Language(s): Java
Created On: October 05, 2012


This article describes the various synchronous and asynchronous approaches for making requests with the AWS SDK for Java. The article concludes with some additional information to help you successfully use the asynchronous operations in the SDK.

The AWS SDK for Java provides synchronous and asynchronous methods that developers can use to call operations on AWS services. The synchronous methods block until the client receives the final response from the service. The asynchronous methods allow you to send a request to a service, and then immediately have control passed back to your code, instead of blocking until the remote service returns a response. This is convenient for many applications, particularly when you are writing user interfaces.

Synchronous Access

The basic pattern for calling an operation on an AWS web service, using the synchronous/blocking clients looks like this:


    AmazonDynamoDB dynamoDB = new AmazonDynamoDBClient(myCredentials);
    try {
        DescribeTableResult result = dynamoDB.describeTable(new DescribeTableRequest().withTableName(myTableName));
        System.out.println("Table: " + result.getTable());
    } catch (AmazonClientException ace) {
        System.out.println("Error describing table: " + ace.getMessage());
    }

In the preceding example, when the dynamoDB.describeTable operation is called, the SDK sends the HTTP request to Amazon DynamoDB, sits and waits for the service to return a response, parses it, and returns a DescribeTableResult back to the caller, which can then continue executing.

Asynchronous Access

The synchronous approach is an easy way to call AWS services, but in some applications, it can be problematic to block until the service has returned a response. For these situations, you can use the asynchronous methods. These methods are non-blocking, that is, they return immediately. The asynchronous methods provide two easy ways for developers to access the service response, once the remote server has finished processing the request:

  • Java Futures—poll the status of the asynchronous request using the standard Java Future interface.
  • Async Callbacks—implement a callback interface and the SDK will call methods on that interface when certain events occur in the lifecycle of the asynchronous request. For example, the SDK calls the interface when a request completes successfully or when a request fails with an error.

Java Futures

One of the most common ways to deal with asynchronous operations in Java is through the java.util.concurrent.Future class. Java Futures allow asynchronous operations to immediately return a result (a Future object) that the caller can poll for the status of the operation. When the caller detects that the operation has completed, the caller can use the Future object to access either the result or any error that might have occurred.

The following code demonstrates how to use Futures to asynchronously perform the same call to Amazon DynamoDB that was shown in the preceding synchronous access section:


    AmazonDynamoDBAsync dynamoDB = new AmazonDynamoDBAsyncClient(myCredentials);
    Future future = dynamoDB.describeTableAsync(new DescribeTableRequest().withTableName(myTableName));
    while (!future.isDone()) {
        Thread.sleep(1000);
        // Do other processing while you're waiting for the response
    }

    try {
        DescribeTableResult result = future.get();
        System.out.println("Table: " + result.getTable());
    } catch (ExecutionException ee) {
        // Futures always wrap errors as an ExecutionException.
        // The *real* exception is stored as the cause of the ExecutionException
        Throwable exception = ee.getCause();
        System.out.println("Error describing table: " + exception.getMessage());
    }

Java Futures allow you to execute operations asynchronously and check on their status, but they still require the developer to poll the Future object to determine if the operation has completed. The next section describes an even easier way to work with asynchronous operations in the SDK: Async Callbacks.

Async Callbacks

In addition to using Java Futures to monitor the status of asynchronous requests, the SDK also allows you to implement a simple asynchronous callback interface that you can pass to the asynchronous method. When various events occur in the asynchronous request's lifecycle, such as when the request completes successfully—or when it fails, the SDK invokes the associated methods in the AsyncHandler implementation, allowing you to easily handle the event.

This code demonstrates how to implement the AsyncHandler interface to have the SDK alert you when your asynchronous request has either completed or failed:


    AmazonDynamoDBAsync dynamoDB = new AmazonDynamoDBAsyncClient(myCredentials);
    dynamoDB.describeTableAsync(new DescribeTableRequest().withTableName(myTableName), 
        new AsyncHandler() {
            public void onSuccess(DescribeTableRequest request, DescribeTableResult result) {
                System.out.println("Table: " + result.getTable());
            }
            
            public void onError(Exception exception) {
                System.out.println("Error describing table: " + exception.getMessage());
                // Callers can also test if exception is an instance of 
                // AmazonServiceException or AmazonClientException and cast 
                // it to get additional information
            }
        });

Java Futures and the AsyncHandler callback interface are completely compatible. You can use both Futures and the callback interface in your code if you want to, but typically one approach will suffice. The major advantage of the callback interface approach is that it frees you from having to poll the Future object to find out when the request has completed. Instead, your code can immediately start its next activity, and rely on the SDK to call your handler at the right time.

Tips and Tricks

Callback Execution

Using AsyncHandler callbacks is ideal for updating a UI with the result of an operation. Keep in mind though that your implementation of AsyncHandler is executed inside the thread pool owned by the asynchronous client. Short, quickly executed code is most appropriate inside your AsyncHandler implementation. Long running or blocking code inside the AsyncHandler implementation can cause contention for the thread pool used by the asynchronous client and can prevent the client from being able to execute requests. If you have a long-running task that needs to begin from a callback, then simply have the callback run the task in a new thread or another thread pool managed by your application.

Thread Pool Configuration

The asynchronous clients in the SDK provide a default thread pool that should work for most applications, but you are also free to supply your own ExecutorService to the asynchronous clients if you want more control over how the thread pools are managed. For example, you could provide your own ExecutorService that uses a custom ThreadFactory to control how threads in the pool are named or to log additional information about thread usage.

Amazon S3 Asynchronous Access

The TransferManager class in the SDK offers asynchronous support for working with the Amazon Simple Storage Service (Amazon S3). The TransferManager manages asynchronous uploads and downloads, provides detailed progress reporting on transfers, and supports callbacks into different events.