AWS Developer Blog

GraalVM Native Image Support in the AWS SDK for Java 2.x

We are excited to announce that AWS SDK for Java 2.x (version 2.16.1 or later) now has out-of-the-box support for GraalVM Native Image compilation.

GraalVM is a universal virtual machine that supports JVM-based languages (e.g. Java, Scala, Kotlin), dynamic languages (e.g. Python, JavaScript), and LLVM-based languages (e.g. C, C++). GraalVM Native Image is one of the most popular features that GraalVM offers. It allows Ahead-of-Time (AoT) compilation of Java programs into a self-contained native executable, called a native image. The executable is optimized and contains everything needed to run the application, and it has faster startup time and smaller heap memory footprint compared with a JVM. Therefore, native images are well-suited for serverless applications where startup latency is critical.

We compared the startup performance of the executable created from GraalVM native image with the same application running on JVM. The test application creates an S3Client with ApacheHttpClient and sends a simple GetObjectRequest. In our testing, we measured the SDK 2.x startup time and the first request latency as well as the memory usage. The results indicate that the native image has significantly faster startup and lower memory consumption.

Due to the nature of AoT, features that rely on dynamic class loading, proxies, or reflections need to be known at build time and require special support. For example, the classes use reflections need to be registered in the native image configuration files. The latest Java SDK 2.x contains such configurations required for SDK classes, and it’s ready to be built into a native image.

The latest release also includes a new Maven Archetype, archetype-app-quickstart, that enables you to quickly bootstrap a native-image compatible Java application configured with the AWS SDK for Java 2.x as a dependency. In this blog, we will show you how to use the archetype to create a new application with GraalVM Native image support.

Getting Started

Prerequisites

This post assumes you have some familiarity with Java programming, the Maven build system, and the GraalVM runtime. To use this tutorial, you need to have the following software installed:

Java 8+
Apache Maven
GraalVM 21.0.0+
GraalVM Native Image

Generating the application

To generate a project, open a terminal (command line) window, run mvn archetype:generate with archetype-app-quickstart. The version of the archetype is the same as the Java SDK 2.x version, and you can use the latest version. There are two modes you can choose: interactive mode and batch mode.

  • Interactive mode

In interactive mode, you’ll be prompted to provide required properties, one at a time.

mvn archetype:generate \
  -DarchetypeGroupId=software.amazon.awssdk \
  -DarchetypeArtifactId=archetype-app-quickstart \
  -DarchetypeVersion=2.16.1
  • Batch mode

In batch mode, you need to provide all required properties at once. Below is the list of the required properties and you can check out all available options here.
Required Properties

    • service: the service client to be used in the lambda function, eg: s3, dynamodb. Only one service should be provided.
    • groupId: the Maven group ID of the application
    • artifactId: the Maven artifact ID of you application
    • httpClient: specifies the http client to be used by the SDK client. Available options are url-connection-client (sync), apache-client (sync) and netty-nio-client (async).
    • nativeImage: specifies whether GraalVM Native Image configuration should be included
mvn archetype:generate \
    -DarchetypeGroupId=software.amazon.awssdk \
    -DarchetypeArtifactId=archetype-app-quickstart \
    -DarchetypeVersion=2.16.1 \
    -DgroupId=com.test \
    -DartifactId=sample-project \
    -Dservice=s3  \
    -DhttpClient=apache-client \
    -DnativeImage=true \
    -Dregion=us-west-2 \
    -DinteractiveMode=false

The generated application has a dependency of AWS SDK for Java 2.16.1. The output will be something like this

[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Archetype: archetype-app-quickstart:2.16.1
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: com.test
[INFO] Parameter: artifactId, Value: sample-project
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: package, Value: com.test
[INFO] Parameter: packageInPathFormat, Value: com/test
[INFO] Parameter: service, Value: s3
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: package, Value: com.test
[INFO] Parameter: nativeImage, Value: true
[INFO] Parameter: httpClient, Value: apache-client
[INFO] Parameter: groupId, Value: com.test
[INFO] Parameter: javaSdkVersion, Value: 2.16.1
[INFO] Parameter: artifactId, Value: sample-project
[INFO] Project created from Archetype in dir: /tmp/sample-project
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  4.004 s
[INFO] Finished at: 2021-02-17T15:07:45-08:00
[INFO] ------------------------------------------------------------------------

When the archetype:generate goal completes, you will have a Maven project with the structure as follows

sample-project
├── README.md
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── test
    │   │           ├── App.java
    │   │           ├── DependencyFactory.java
    │   │           └── Handler.java
    │   └── resources
    │       └── simplelogger.properties
    └── test
        └── java
            └── com
                └── test
                    └── HandlerTest.java

The App.java class is the entry point for the application. The DependencyFactory.java class contains the configured SDK client.

Building the application

To build the application, run the following commands:

# Go to project directory created. 
# The name should be the same as the artifactId provided
cd sample-project

# Build the application
mvn clean package

Creating a native executable

You can run the following command to create a native image:

mvn clean package -P native-image

Once it completes, it will generate an executable in the target folder. The name should be the same as the artifactId provided.

-H:ReflectionConfigurationResources=META-INF/native-image/software.amazon.awssdk/s3/reflect-config.json \
-H:EnableURLProtocols=https \
-H:+ReportUnsupportedElementsAtRuntime \
-H:ReflectionConfigurationResources=META-INF/native-image/software.amazon.awssdk/sdk-core/reflect-config.json \
-H:ResourceConfigurationResources=META-INF/native-image/software.amazon.awssdk/sdk-core/resource-config.json \
-H:ReflectionConfigurationResources=META-INF/native-image/software.amazon.awssdk/apache-client/reflect-config.json \
-H:ResourceConfigurationResources=META-INF/native-image/software.amazon.awssdk/apache-client/resource-config.json \
-H:DynamicProxyConfigurationResources=META-INF/native-image/software.amazon.awssdk/apache-client/proxy-config.json \
-H:FallbackThreshold=0 \
-H:ClassInitialization=org.slf4j:build_time \
-H:Class=com.test.App \
-H:Name=sample-project \
-H:CLibraryPath=/Library/Java/JavaVirtualMachines/graalvm-ce-java8-21.0.0/Contents/Home/jre/lib/svm/clibraries/darwin-amd64 \

]
[sample-project:33940]    classlist:   6,671.68 ms,  1.36 GB
[sample-project:33940]        (cap):   4,022.10 ms,  1.36 GB
[sample-project:33940]        setup:   6,922.21 ms,  1.36 GB
[sample-project:33940]     (clinit):   1,352.10 ms,  2.80 GB
[sample-project:33940]   (typeflow):  33,266.42 ms,  2.80 GB
[sample-project:33940]    (objects):  16,911.48 ms,  2.80 GB
[sample-project:33940]   (features):   1,717.77 ms,  2.80 GB
[sample-project:33940]     analysis:  54,700.08 ms,  2.80 GB
[sample-project:33940]     universe:   4,559.62 ms,  2.81 GB
[sample-project:33940]      (parse):  13,027.95 ms,  3.06 GB
[sample-project:33940]     (inline):   9,664.36 ms,  3.70 GB
[sample-project:33940]    (compile):  73,425.18 ms,  4.75 GB
[sample-project:33940]      compile: 100,632.59 ms,  4.75 GB
[sample-project:33940]        image:  10,399.21 ms,  4.82 GB
[sample-project:33940]        write:   1,834.74 ms,  4.82 GB
[sample-project:33940]      [total]: 186,280.64 ms,  4.82 GB
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  03:12 min
[INFO] Finished at: 2021-02-17T15:12:31-08:00
[INFO] ------------------------------------------------------------------------

Running the native executable

To execute the native image, you can run the following command

target/sample-project

The output should be similar to the following:

2021-02-17 15:13:18:071 -0800 [main] INFO com.test.App - Application starts
2021-02-17 15:13:18:086 -0800 [main] INFO com.test.App - Application ends

Conclusion

In this blog post we showed you the benefits of using native image compiled of applications using the AWS SDK for Java 2x. We also showed you how quick it is to use the new archetype to get start with native-image compatible applications using AWS SDK for Java 2.x. If you want to learn more about the archetype, you can check out the archetype-app-quickstart source code on GitHub. Please give this new archetype a try and let us know what you think via the GitHub issues page.

Zoe Wang

Zoe Wang

Zoe is a Software Development Engineer working on the AWS SDK for Java. She is passionate about building tools to make software development easier. You can find her on GitHub @zoewangg.