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.

Prerequisites

Add the GraalVM native image build plugin.

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.

pom.xml

<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>

Meet the requirements of the Lambda custom runtime

<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.

bootstrap

#!/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.

zip.xml

<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>