Beat Java cold starts in AWS Lambda's with GraalVM
Published: 2021-02-01 10:09:00

---
title: Beat Java cold starts in AWS Lambda's with GraalVM
published: true
date: 2020-10-19 00:00:00 UTC
tags: 
canonical_url: https://www.kabisa.nl/tech/beat-java-cold-starts-in-aws-lambdas-with-graalvm
---

Let's face it, against all advice from your co-workers, friends, or even family, you still want to build a blazingly fast lambda with **Java**. Either to prove a point, to win a bet, or because you are just as much a Java geek as me. 

With this blogpost I'm going to walk you through pre-compiling your Java lambda with GraalVM [[1]](https://www.graalvm.org/docs/introduction/) to eliminate slow cold-start times.

## Cold starts, what, where, and when?
On the first invocation of your lambda, AWS boots up a container with the right runtime before actually calling your code. When using runtimes like NodeJs or Python, the cold start times are between 200-250 ms [[2]](https://levelup.gitconnected.com/aws-lambda-cold-start-language-comparisons-2019-edition-%EF%B8%8F-1946d32a0244) while with Java it takes atleast 650 ms if not more depending on the specifics of your function code.

Java's cold start is mainly caused by the JVM, which is started by default when using AWS' pre-configured Java runtime. Luckily AWS also provides us the option to create our own `custom runtime` which allows us to use GraalVM to pre-compile our Java code into a binary that can be ran without the need of a JVM.

## Let's get practical
Let's take a simple Java program like our Lambda Authorizer from the previous blogpost [[3]](https://www.kabisa.nl/tech/enriching-requests-with-an-aws-lambda-authorizer/), which can be found on Github [[4]](https://github.com/VR4J/aws-enriching-lambda-authorizer), and start enabling it for GraalVM processing.

### AWS Lambda Runtime API 
As we are going to build our own custom runtime, we have to interact with the AWS Lambda Runtime API [[5]](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html) ourselves.
On this API there are three endpoints that we want to use:
* `/runtime/invocation/next` to get our next invocation, 
* `/runtime/invocation/{AwsRequestId}/response` to post the response to a specific invocation (request)
* `/runtime/invocation/{AwsRequestId}/error` to post errors during the invocation (request).

A simple bootstrap class to handle this polling of the AWS Runtime API can be found on Github [[6]](https://github.com/VR4J/aws-enriching-lambda-authorizer/blob/feature/graal-vm/src/main/java/nl/theguild/lambda/Main.java).

### Reflection
One of the downsides of pre-compiling with GraalVM is that it can't handle reflection very well, which means that we have to create a `reflect.json` configuration file, where we can point GraalVM to the classes it should prepare for reflective use. Since we use Jackson as our serialization library we should configure all classes that need (de)serialization in the `reflect.json` file.

Example entry in the `reflect.json` configuration file:

```json
[
  ...
  {
    "name": "nl.theguild.lambda.model.DefaultResponse",
    "allPublicMethods" : true
  },
  ...
]
```

For the complete `reflect.json` check Github [[7]](https://github.com/VR4J/aws-enriching-lambda-authorizer/blob/feature/graal-vm/reflect.json)

### Compiling the image!
Yes! The interesting part! Now that we prepared our little Java project for native-image compilation let's see how it is done.

First we need to make sure our jar comes with a proper manifest stating our mainClass, which can be done by adding the following config to our `pom.xml`.

```xml
<plugin>
    <artifactId>maven-jar-plugin</artifactId>
    <version>3.0.2</version>
    <configuration>
        <archive>
            <manifest>
                <mainClass>nl.theguild.lambda.Main</mainClass>
            </manifest>
        </archive>
    </configuration>
</plugin>
```

We then can start creating a simple bash script that will preform all the necessary steps to end up with our custom-runtime zip.

Step 1: Create our jar file.
```sh
mvn clean install;
```

Step 2: Run GraalVM inside a docker container, and mount our project.
```sh
docker run --rm --name graal -v $(pwd):/${PATH_TO_PROJECT} oracle/graalvm-ce:19.2.0
``` 
Step 3: Install and run GraalVM's `native-image` command while including our `reflect.json` configuration, and enabling http.
```sh
gu install native-image;
native-image \
    -H:EnableURLProtocols=http \
    -H:ReflectionConfigurationFiles=${PATH_TO_PROJECT}/reflect.json \
    -jar ${PATH_TO_JAR};
```

Step 4: Move our native image to a folder which we can later on `zip` for usage in the AWS Lambda.
```sh
mkdir /${PATH_TO_PROJECT}/target/custom-runtime;
cp ${BINARY_RESULT} ${PATH_TO_PROJECT}/target/custom-runtime;
```

Step 5: Create a `bootstrap` file that instructs AWS on how to run the native image.

AWS defines in their documentation [[8]](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html) that every custom-runtime should be a zip including a `bootstrap` shell file that can jump start the function.

In our case that bootstrap file can be a simple one looking something like this:
```sh
#!/bin/sh
set -euo pipefail
./${PROJECT_NAME}
```

Adding all those steps into one single bash script results in the following file:

```sh
PROJECT_NAME=aws-enriching-lambda-authorizer
PROJECT_VERSION=1.0.0

# Generate Jar file
mvn clean install;

# Generate Native Image
docker run --rm --name graal -v $(pwd):/${PROJECT_NAME} oracle/graalvm-ce:19.2.0 \
    /bin/bash -c "gu install native-image; \
                  native-image \
		                -H:ReflectionConfigurationFiles=/${PROJECT_NAME}/reflect.json \
                    -jar /${PROJECT_NAME}/target/${PROJECT_NAME}-${PROJECT_VERSION}.jar \
                    ; \
                    mkdir /${PROJECT_NAME}/target/custom-runtime \
                    ; \
                    cp ${PROJECT_NAME}-${PROJECT_VERSION} /${PROJECT_NAME}/target/custom-runtime/${PROJECT_NAME}";

echo -e "#!/bin/sh \n \
set -euo pipefail \n \
./${PROJECT_NAME}" > target/custom-runtime/bootstrap;

# Make bootstrap executable
chmod +x target/custom-runtime/bootstrap;

# Zip
rm $PROJECT_NAME-custom-runtime.zip
cd target/custom-runtime || exit
zip -X -r ../../$PROJECT_NAME-custom-runtime.zip .
```

## HOORAY!

We now have our `aws-enriching-lambda-authorizer-custom-runtime.zip` that we can use in our AWS Lambda and enjoy our rapid fast cold start timings... WHILE USING JAVA!

Navigate to the `AWS Console` -> `Lambda` -> `Create function`, choose `custom-runtime -> use default bootstrap` and click `create`.

Now that we have a lambda, we can go to `Actions` and upload our zip file, after which we can configure a test-event.

As our authorizer lambda is triggered by the api-gateway we can use the default `Amazon API Gateway AWS Proxy` event, and simply add an `"Authorization": "Bearer 12345"` header to the request.

When we now hit `test` we see our lambda responding in just `283ms`, matching the cold-start times of other runtimes, show that to your co-workers, friends, and family! 

![](https://i.imgur.com/ZPZ953W.png)

## Source

Github: [https://github.com/VR4J/aws-enriching-lambda-authorizer/tree/feature/graal-vm](https://github.com/VR4J/aws-enriching-lambda-authorizer/tree/feature/graal-vm)

## References

[1] [https://www.graalvm.org/docs/introduction/](https://www.graalvm.org/docs/introduction/)
[2] [https://levelup.gitconnected.com](https://levelup.gitconnected.com/aws-lambda-cold-start-language-comparisons-2019-edition-%EF%B8%8F-1946d32a0244)<br/>
[3] [https://www.kabisa.nl/tech/enriching-requests-with-an-aws-lambda-authorizer](https://www.kabisa.nl/tech/enriching-requests-with-an-aws-lambda-authorizer)<br/>
[4] [https://github.com/VR4J/aws-enriching-lambda-authorizer](https://github.com/VR4J/aws-enriching-lambda-authorizer)<br/>
[5] [https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html)<br/>
[6] [https://github.com/VR4J/aws-enriching-lambda-authorizer/blob/feature/graal-vm/src/main/java/nl/theguild/lambda/Main.java](https://github.com/VR4J/aws-enriching-lambda-authorizer/blob/feature/graal-vm/src/main/java/nl/theguild/lambda/Main.java)<br/>
[7] [https://github.com/VR4J/aws-enriching-lambda-authorizer/blob/feature/graal-vm/reflect.json](https://github.com/VR4J/aws-enriching-lambda-authorizer/blob/feature/graal-vm/reflect.json)<br/>
[8] [https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html)<br/>