Spring Native + AWS Lambda: Part 2 - The Build

Part 2 is of this series is all about compiling our Function to a native executable and bundling it with a bootstrap file for AWS Lambda.

Table of Contents:

Getting Started

For compiling and packaging we will use the following plugins:

  • native-maven-plugin for compiling the native image
  • maven-assembly-plugin for bundling the image with a bootstrap script


Referring back to the scenario in Part 1, we want to deploy our function to AWS Lambda, which uses Amazon Linux 2.

Building a native image requires installing build tools, GraalVM, and matching libraries used in Amazon Linux 2. We can do this either locally, or within a Docker container. Spring initializr configures paketobuildpacks to create an image of our application using Docker. This approach is ideal if you are deploying a REST endpoint to Kubernetes or AWS Fargate. For our scenario, want to upload the code to AWS Lambda, which is why we will use our own build container.

If you want to install locally, instructions can be found in the Spring Native documentation.

Using Amazon Linux 2 as a base image, we can install GraalVM and native-image build tools to compile our application to be compatible with AWS. I have uploaded such an image to Dockerhub. We will use this image, as well as docker-compose to build our native-images.

Building The Native Image

Let's create a docker-compose.yml file to get started.

version: '3.1'
services:
  lambda:
    image: dyniri/spring-native-aws-builder:java-11
    volumes:
      - ./:/app
docker-compose.yml

The image assumes you are skipping tests and the profile name in pom.xml is native. You can see configuration options on DockerHub.

The builder image uses Amazon Linux 2 as a base and has everything necessary to compile our code. Edit command if you want to enable tests or have a different profile name. Before we can build, we need to mount our source code into the container as a volume.

One last bit of code before building. When compiling within the container, the target/ directory is owned by the user inside the container. This script will build and change permissions for us.

#!/bin/bash

docker-compose up
sudo chown -R $(whoami):$(whoami) target/
build.sh

With our docker-compose.yml and build.sh files ready, we can compile. Run the build script and sit back. My builds tend to take between 1 and 2 minutes. If all goes well, you will be presented with a build success message.

lambda_1  | [INFO] ------------------------------------------------------------------------
lambda_1  | [INFO] BUILD SUCCESS
lambda_1  | [INFO] ------------------------------------------------------------------------
lambda_1  | [INFO] Total time:  01:02 min
lambda_1  | [INFO] Finished at: 2021-10-01T18:34:46Z
lambda_1  | [INFO] ------------------------------------------------------------------------
Successful build message

A common error that can occur is Error: Image build request failed with exit status 137, which means the container ran out of memory. Make sure you have at least 6GB+ for Docker to use for the build process.

Update: As of GraalVM 22.2, memory usage has been drastically reduced, allowing you to build with much less memory. As a result, it may take longer to compile. Highly recommended to still have 6GB+.

Congratulations! You have created your first Spring Native executable! Inside your target directory, you should see the build artifacts.

target/
    ├── cloud-function-dynamodb-lambda
    ├── cloud-function-dynamodb-lambda-exec.jar
    ├── cloud-function-dynamodb-lambda.jar
    └── cloud-function-dynamodb-lambda-native.zip

cloud-function-dynamodb-lambda is our native image and can be run using ./target/cloud-function-dynamodb-lambda, but it will crash with java.lang.NoClassDefFoundError: org.springframework.cloud.function.web.source.DestinationResolver. This is intended as the application is looking for AWS specific infrastructure that we do not have. Finally, cloud-function-dynamodb-lambda-native.zip is our deployment package and will be used in Part 3 to deploy our function to AWS.

Recap of Part 2

  • native-maven-plugin for compiling the native image
  • maven-assembly-plugin for bundling the image with a bootstrap script
  • Docker container as a build environment against Amazon Linux 2

In the final part, we will deploy our function as an AWS Lambda Function.

Full source code can be found on my Github
Click here for Part 3 - The Deploy