AWS SDK for Java version 2
Developer Guide

Asynchronous Programming

The AWS SDK for Java 2.0 features truly nonblocking asychronous clients that implement high concurrency across a few threads. The AWS SDK for Java 1.11.x has asynchronous clients that are wrappers around a thread pool and blocking synchronous clients that don't provide the full benefit of nonblocking I/O.

Synchronous methods block your thread's execution until the client receives a response from the service. Asynchronous methods return immediately, giving control back to the calling thread without waiting for a response.

Because an asynchronous method returns before a response is available, you need a way to get the response when it's ready. The AWS SDK for Java 2.0 asynchronous client methods return CompletableFuture objects that allow you to access the response when it's ready.

Non-Streaming Operations

For non-streaming operations, asychronous method calls are similar to synchronous methods. However, the asynchronous methods in the AWS SDK for Java return a CompletableFuture object that contains the results of the asynchronous operation in the future.

Call the CompletableFuture whenComplete() method with an action to complete when the result is available. CompletableFuture implements the Future interface so you can also get the response object by calling the get() method as well.

The following is an example of an asynchronous operation that calls a Amazon DynamoDB function to get a list of tables, receiving a CompletableFuture that can hold a ListTablesResponse object. The action defined in the call to whenComplete() is done only when the asynchronous call is complete.

Imports

import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider; import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient; import software.amazon.awssdk.services.dynamodb.model.ListTablesRequest; import software.amazon.awssdk.services.dynamodb.model.ListTablesResponse; import software.amazon.awssdk.utils.FunctionalUtils; import java.util.List; import java.util.concurrent.CompletableFuture;

Code

public class DynamoDBAsync { public static void main(String[] args) throws InterruptedException { // Creates a default async client with credentials and regions loaded from the environment DynamoDbAsyncClient client = DynamoDbAsyncClient.create(); CompletableFuture<ListTablesResponse> response = client.listTables(ListTablesRequest.builder() .build()); // Map the response to another CompletableFuture containing just the table names CompletableFuture<List<String>> tableNames = response.thenApply(ListTablesResponse::tableNames); // When future is complete (either successfully or in error) handle the response tableNames.whenComplete((tables, err) -> { try { if (tables != null) { tables.forEach(System.out::println); } else { // Handle error err.printStackTrace(); } } finally { // Lets the application shut down. Only close the client when you are completely done with it. client.close(); } }); tableNames.join(); } }

Streaming Operations

For streaming operations, you must provide an AsyncRequestBody to provide the content incrementally, or an AsyncResponseTransformer to receive and process the response.

The following example uploads a file to Amazon S3 asynchronously by using the PutObject operation.

Imports

import software.amazon.awssdk.core.async.AsyncRequestBody; import software.amazon.awssdk.services.s3.S3AsyncClient; import software.amazon.awssdk.services.s3.model.PutObjectRequest; import software.amazon.awssdk.services.s3.model.PutObjectResponse; import software.amazon.awssdk.utils.FunctionalUtils; import java.nio.file.Paths; import java.util.concurrent.CompletableFuture;

Code

public class S3AsyncOps { private static final String BUCKET = "sample-bucket"; private static final String KEY = "testfile.in"; public static void main(String[] args) { S3AsyncClient client = S3AsyncClient.create(); CompletableFuture<PutObjectResponse> future = client.putObject( PutObjectRequest.builder() .bucket(BUCKET) .key(KEY) .build(), AsyncRequestBody.fromFile(Paths.get("myfile.in")) ); future.whenComplete((resp, err) -> { try { if (resp != null) { System.out.println("my response: " + resp); } else { // Handle error err.printStackTrace(); } } finally { // Lets the application shut down. Only close the client when you are completely done with it. client.close(); } }); future.join(); } }

The following example gets a file from Amazon S3 asynchronously by using the GetObject operation.

Imports

import software.amazon.awssdk.core.async.AsyncResponseTransformer; import software.amazon.awssdk.services.s3.S3AsyncClient; import software.amazon.awssdk.services.s3.model.GetObjectRequest; import software.amazon.awssdk.services.s3.model.GetObjectResponse; import software.amazon.awssdk.utils.FunctionalUtils; import java.nio.file.Paths; import java.util.concurrent.CompletableFuture;

Code

public class S3AsyncStreamOps { private static final String BUCKET = "sample-bucket"; private static final String KEY = "testfile.out"; public static void main(String[] args) { S3AsyncClient client = S3AsyncClient.create(); final CompletableFuture<GetObjectResponse> futureGet = client.getObject( GetObjectRequest.builder() .bucket(BUCKET) .key(KEY) .build(), AsyncResponseTransformer.toFile(Paths.get("myfile.out"))); futureGet.whenComplete((resp, err) -> { try { if (resp != null) { System.out.println(resp); } else { // Handle error err.printStackTrace(); } } finally { // Lets the application shut down. Only close the client when you are completely done with it client.close(); } }); futureGet.join(); } }

Advanced Operations

The AWS SDK for Java 2.0 uses Netty an asynchronous event-driven network application framework, to handle I/O threads. The AWS SDK for Java 2.0 creates an ExecutorService behind Netty, to complete the futures returned from the HTTP client request through to the Netty client. This abstraction reduces the risk of an application breaking the async process if developers choose to stop or sleep threads. By default, 50 Threads are generated for each asychronous client, and managed in a queue within the ExecutorService.

Advanced users can specify their thread pool size when creating an asychronous client using the following option when building.

Code

S3AsyncClient clientThread = S3AsyncClient.builder() .asyncConfiguration( b -> b.advancedOption(SdkAdvancedAsyncClientOption .FUTURE_COMPLETION_EXECUTOR, Executors.newFixedThreadPool(10) ) ) .build();

To optimize performance, you can manage your own thread pool executor, and include it when configuring your client.

ThreadPoolExecutor executor = new ThreadPoolExecutor(50, 50, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10_000), new ThreadFactoryBuilder() .threadNamePrefix("sdk-async-response").build()); // Allow idle core threads to time out executor.allowCoreThreadTimeOut(true); S3AsyncClient clientThread = S3AsyncClient.builder() .asyncConfiguration( b -> b.advancedOption(SdkAdvancedAsyncClientOption .FUTURE_COMPLETION_EXECUTOR, executor ) ) .build();

If you prefer to not use a thread pool, at all, use Runnable::run instead of using a thread pool executor.

S3AsyncClient clientThread = S3AsyncClient.builder() .asyncConfiguration( b -> b.advancedOption(SdkAdvancedAsyncClientOption .FUTURE_COMPLETION_EXECUTOR, Runnable::run ) ) .build();