Tuning the AWS Java SDK 2.x to reduce startup time

One of the most asked feature requests we’ve received from AWS Java SDK customers is to improve SDK startup latency, and in the development of AWS Java SDK 2.x, we’ve put a focus on SDK cold startup time for AWS Lambda functions. In this blog post, we will share the best practices on how to optimize your Lambda function using the AWS Java SDK 2.x, so that you can achieve minimal SDK startup time.

Use the built-in HttpUrlConnection client to reduce instantiation time

The AWS Java SDK 2.x includes a pluggable HTTP layer that allows customers to switch to different HTTP implementations. Three HTTP clients are supported out-of-the-box: Apache HTTP client , Netty HTTP client and Java HTTP URL Connection client. With the default configuration, Apache HTTP client and Netty HTTP client are used for synchronous clients and asynchronous clients respectively. They are powerful HTTP clients with more features. However, they come at the cost of higher instantiation time. On the other hand, the JDK built-in HTTPUrlConnection library is more lightweight and has lower instantiation time. In addition, as the HTTPUrlConnection is part of the JDK, using it will not bring in external dependencies. It will allow you to keep the deployment package size small and thus, reduce the amount of time it takes for the deployment package to be unpacked and downloaded. Hence, we recommend using HttpUrlConnectionClient when configuring the SDK client. Note that it only supports synchronous API calls. If you’d like to see support for asynchronous SDK clients with JDK 11 built-in HTTP client, upvote this GitHub issue.

Use the EnvironmentVariableCredentialProvider for credential lookups

With the default configuration, the SDK will automatically determine which credential provider to use by looking at various places such as system properties, credential files, etc . By using EnvironmentVariableCredentialProvider that loads credentials from Lambda environment variables, you can avoid the amount of time spent on the lookups.

Set the region from the AWS_REGION environment variable

Similar to the credential provider, the SDK can also automatically determine which region to use through different sources. By retrieving the region from Lambda provided AWS_REGION environment variable and setting it directly on the builder, no additional time is spent on initializing the region provider chain and region lookups.
Here’s the sample code of the recommended client configuration

 S3Client.builder()
         .credentialsProvider(EnvironmentVariableCredentialsProvider.create())
         .region(Region.of(System.getenv(SdkSystemSetting.AWS_REGION.environmentVariable())))
         .httpClientBuilder(UrlConnectionHttpClient.builder())
         .build();

Exclude unused SDK HTTP dependencies

The SDK by default includes Apache HTTP client and Netty HTTP client dependencies. If startup time is important to your application and you do not need both implementations, we recommend excluding unused SDK HTTP dependencies to minimize the deployment package size. Below is the sample Maven POM file for an application that only uses url-connection-client and excludes netty-nio-client and apache-client

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>software.amazon.awssdk</groupId>
                <artifactId>bom</artifactId>
                <version>${aws.java.sdk.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>software.amazon.awssdk</groupId>
            <artifactId>s3</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>software.amazon.awssdk</groupId>
                    <artifactId>netty-nio-client</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>software.amazon.awssdk</groupId>
                    <artifactId>apache-client</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>software.amazon.awssdk</groupId>
            <artifactId>url-connection-client</artifactId>
        </dependency>
    </dependencies>

Initialize the SDK client outside of the Lambda function handler

After a Lambda function finishes, Lambda may reuse the previous container for subsequent invocations. In such case, objects declared outside of the function handler method remain initialized. To take the advantage of the Lambda execution context reuse, we recommend initializing the SDK client outside of the handler method so that subsequent executions processed by the same instance can reuse the client and connections. Below is the sample code of Lambda function handler.

public class App implements RequestHandler<Object, Object> {
    private final S3Client s3Client;

    public App() {
        s3Client = DependencyFactory.s3Client();
    }

    @Override
    public Object handleRequest(final Object input, final Context context) {
         s3Client.listBuckets();
        // Function logic here
    }
}

Conclusion

In this blog post, we showed you our recommendations to tune the AWS Java SDK 2.x for reducing startup time. We also have a Maven archetype that allows you to create a Java Lambda function quickly with best practices incorporated. You can check out this blog post to learn more. With our blog posts, we will educate the AWS developer community on performance-related best practices and how to implement them in your code, For updates to the AWS SDK for Java libraries, check out the aws-java-sdk-v2 repo on GitHub and let us know if you have any feedback/comments/issues via the GitHub issues page.