Converting Java Lambda functions to GraalVM native-image
There are a series of changes that will need to be made in order for this project to produce a suitable native binary which can be run on AWS Lambda.
- Install GraalVM
- Install native-image tool.
You can use a maven profile to toggle which type of build you want. This means you can continue to iterate quickly with the standard build and only build the slower native image when necessary.
<profiles>
<profile>
<id>native-image</id>
<build>
<plugins>
<plugin>
<groupId>org.graalvm.nativeimage</groupId>
<artifactId>native-image-maven-plugin</artifactId>
<version>21.1.0</version>
<executions>
<execution>
<goals>
<goal>native-image</goal>
</goals>
</execution>
</executions>
<configuration>
<imageName>product-binary</imageName>
<mainClass>com.amazonaws.services.lambda.runtime.api.client.AWSLambda</mainClass>
<buildArgs combine.children="append">
--verbose
--no-fallback
--initialize-at-build-time=org.slf4j
--enable-url-protocols=http
-H:+AllowIncompleteClasspath
</buildArgs>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
Maven uses the -P
flag to activate a profile.
mvn clean install -P native-image
Within the configuration the main class is set to AWSLambda
. This class is the implementation of the
runtime interface client. A reference to our handler will be passed as an argument to it. So that
it knows which class we want it to run.
<mainClass>com.amazonaws.services.lambda.runtime.api.client.AWSLambda</mainClass>
- Add the AWS Java runtime interface client dependency
- Add the bootstrap file
- Package it all up into a zip file
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-runtime-interface-client</artifactId>
<version>1.1.0</version>
</dependency>
I keep the bootstrap file in src/main/config
there might be a more correct place.
└── products
└── src
└── main
└── config
└── bootstrap
The contents is very simple. Execute my binary passing the $_HANDLER
environment variable as
the only argument. This is the value you give in the handler
attribute when you create a
Lambda function.
#!/usr/bin/env bash
./product-binary $_HANDLER
I could hard code the package, class and method here instead of using the env var, but I like this way as I imagine it’ll reduce copy and paste errors.
Finally use the maven-assembly-plugin
to create a zip file including both the native binary
and the bootstrap. Add the plugin definition to the native-image
profile. This will create a
final zip file named function.zip
.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.3.0</version>
<executions>
<execution>
<id>zip-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<finalName>function</finalName>
<descriptors>
<descriptor>src/assembly/zip.xml</descriptor>
</descriptors>
<attach>false</attach>
<appendAssemblyId>false</appendAssemblyId>
</configuration>
</execution>
</executions>
</plugin>
The assembly is located here.
└── products
└── src
└── assembly
└── zip.xml
The assembly descriptor is this.
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.1.0 http://maven.apache.org/xsd/assembly-2.1.0.xsd">
<id>lambda-package</id>
<formats>
<format>zip</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<files>
<file>
<source>${project.build.directory}${file.separator}product-binary</source>
<outputDirectory>${file.separator}</outputDirectory>
<destName>product-binary</destName>
<fileMode>777</fileMode>
</file>
<file>
<source>src${file.separator}main${file.separator}config${file.separator}bootstrap</source>
<outputDirectory>${file.separator}</outputDirectory>
<destName>bootstrap</destName>
<fileMode>777</fileMode>
</file>
</files>
</assembly>