The goal of this guide is to demonstrate how Java can compete effectively in the serverless space. We will build a native executable using GraalVM's Native Image technology and deploy it on AWS Lambda.
Custom Lambda Runtimes
AWS Lambda supports managed runtimes for most prominent programming languages, including Java. However, in this tutorial, we won't use the AWS runtime for Java because the objective is to avoid running on the JVM.
AWS Lambda allows you to run custom runtimes directly on Amazon Linux 2 (AL2). This enables us to run a native binary, provided it is built for AL2 and the x86_64/arm64 architecture.
To deploy a custom runtime, you need to provide a zip archive containing a bootstrap file and the function being invoked:
.
├── bootstrap
├── function.sh
Example Bootstrap
bootstrap
#!/bin/sh
set -euo pipefail
# Initialization - load function handler
source $LAMBDA_TASK_ROOT/"$(echo $_HANDLER | cut -d. -f1).sh"
# Processing
while true
do
HEADERS="$(mktemp)"
# Get an event. The HTTP request will block until one is received
EVENT_DATA=$(curl -sS -LD "$HEADERS" "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next")
# Extract request ID by scraping response headers received above
REQUEST_ID=$(grep -Fi Lambda-Runtime-Aws-Request-Id "$HEADERS" | tr -d '[:space:]' | cut -d: -f2)
# Run the handler function from the script
RESPONSE=$($(echo "$_HANDLER" | cut -d. -f2) "$EVENT_DATA")
# Send the response
curl "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/$REQUEST_ID/response" -d "$RESPONSE"
done
Grant the necessary permissions and zip the files together:
chmod 755 function.sh bootstrap
zip lambda.zip bootstrap function.sh
Configure the Lambda as follows:
Set the correct entry point after creation:
Finally, upload your zip file:
Congratulations, you are now running a custom runtime!
Compile GraalVM Native Image
Prepare Build Environment
GraalVM does not currently support cross-platform compilation. Therefore, you need to build your native executable on the same platform where the Lambda function will run, i.e., AL2 amd64.
We will use a custom AL2 Docker image that includes GraalVM for JDK 17, along with Gradle and Maven build tools.
Dockerfile
FROM amazonlinux:2
RUN yum -y update \
&& yum install -y tar unzip gzip bzip2-devel ed gcc gcc-c++ gcc-gfortran \
less libcurl-devel openssl openssl-devel readline-devel xz-devel \
zlib-devel glibc-static libcxx libcxx-devel llvm-toolset-7 zlib-static \
&& rm -rf /var/cache/yum
ENV GRAAL_VERSION 22.3.0
ENV GRAAL_FOLDERNAME graalvm-ce-java17-${GRAAL_VERSION}
ENV GRAAL_FILENAME graalvm-ce-java17-linux-amd64-${GRAAL_VERSION}.tar.gz
RUN curl -4 -L https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-${GRAAL_VERSION}/${GRAAL_FILENAME} | tar -xvz
RUN mv $GRAAL_FOLDERNAME /usr/lib/graalvm
RUN rm -rf $GRAAL_FOLDERNAME
# Graal Maven plugin requires Maven 3.3.x
ENV MVN_VERSION 3.8.8
ENV MVN_FOLDERNAME apache-maven-${MVN_VERSION}
ENV MVN_FILENAME apache-maven-${MVN_VERSION}-bin.tar.gz
RUN curl -4 -L https://mirrors.ukfast.co.uk/sites/ftp.apache.org/maven/maven-3/${MVN_VERSION}/binaries/${MVN_FILENAME} | tar -xvz
RUN mv $MVN_FOLDERNAME /usr/lib/maven
RUN rm -rf $MVN_FOLDERNAME
# Gradle
ENV GRADLE_VERSION 7.4.1
ENV GRADLE_FOLDERNAME gradle-${GRADLE_VERSION}
ENV GRADLE_FILENAME gradle-${GRADLE_VERSION}-bin.zip
RUN curl -LO https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip
RUN unzip gradle-${GRADLE_VERSION}-bin.zip
RUN mv $GRADLE_FOLDERNAME /usr/lib/gradle
RUN rm -rf $GRADLE_FOLDERNAME
VOLUME /project
WORKDIR /project
RUN /usr/lib/graalvm/bin/gu install native-image
RUN ln -s /usr/lib/graalvm/bin/native-image /usr/bin/native-image
RUN ln -s /usr/lib/maven/bin/mvn /usr/bin/mvn
RUN ln -s /usr/lib/gradle/bin/gradle /usr/bin/gradle
ENV JAVA_HOME /usr/lib/graalvm
ENTRYPOINT ["sh"]
Build the Docker image to serve as your build environment:
docker build -t al2graalvm:1.0 .
Run the Build on AL2
Use the AL2 GraalVM Docker image to compile a native image binary:
With Gradle:
gradle nativeCompile
Gradle will output the native image executable to:
Re-upload the newly created zip file and test your function:
This is IntelliJ's Java "Hello World" example.
Summary
This guide provides a quick setup for deploying GraalVM on AWS Lambda. In the next article, we will cover a real-world example using a Spring Boot application in future articles.