Enjoy sub 500ms cold starts and significantly less memory and CPU utilisation with GraalVM native images.

TL;DR

  • Finally, Spring Boot on Lambda is viable – bring your Java Spring skills to the serverless party
  • Generate native platform executables of Spring Boot apps using GraalVM
  • Deploy the native image to Lambda and achieve sub 500ms cold starts and significantly less CPU and memory usage
  • Achieve greater sustainability with lower energy consumption and carbon footprint

Intro

Spring Boot on Lambda is finally viable. Using Graal VM and Spring Native it is possible to create native executables for running custom runtimes on Lambda. Native executables are super fast, result in sub 500ms cold starts on Lambda, and use way less CPU, memory, and energy to run.

Java tooling is excellent, and Spring Boot provides an opinionated approach to developing applications that allows you to spend more time concentrating on delivering business value. With just a few modifications you can bring your Spring knowledge to the Lambda party, reduce the cognitive burden of adopting new languages and tools, and embrace serverless, fast.

Spring Native is still experimental - so proceed with caution - however the results are very encouraging indeed.

Prereqs

  • Git
  • Docker
  • 8GB free RAM

Modify some files

I used the official Spring Native “AWS Lambda custom runtime example” and got it running in a Lambda. It required minor changes to two files to get it to work. I forked the official Spring Native repo and added the modifications which you can find here.

Clone: https://github.com/spring-projects-experimental/spring-native

Modify spring-native/samples/cloud-function-aws/src/main/java/com/example/demo/DemoApplication.java

To look like this

@TypeHint(types = GenericApplicationContext.class,
typeNames = "org.springframework.context.support.GenericApplicationContext$init")
@SpringBootApplication
public class DemoApplication implements ApplicationContextInitializer<GenericApplicationContext> {

    public static void main(final String[] args) {
        FunctionalSpringApplication.run(DemoApplication.class, args);
    }

    @Override
    public void initialize(final GenericApplicationContext context) {
        context.registerBean("nameUppercaser",
                FunctionRegistration.class,
                () -> new FunctionRegistration<>(new NameUppercaser()).type(NameUppercaser.class));
    }
}

Start up time is faster when you use the new “functional” style of bean declaration introduced in Spring 5. The above code uses this method to declare the NameUpperCaser function.

We also need a @TypeHint annotation to tell GraalVM to include GenericApplicationContext$init when generating a native executable.

Now add a line to spring-native/samples/cloud-function-aws/src/main/resources/application.properties

So it looks like this

debug=true
spring.main.web-application-type=none

Setting spring.main.web-application-type to none tells Spring to not start the embedded HTTP server.

That’s it for modifications.

Build the executable

$> cd <repo-root>
$> ./run-dev-container.sh

That will download and start the docker container where you can now build the project:

$> cd samples/cloud-function-aws
$> ./build.sh

Warning! The build takes quite a bit of RAM - 8GB on my machine.

Deploy and test

Navigate to AWS Lambda dashboard and create a new function (name it anyway you want). In “Runtime” go to Custom Runtime and select one of the options available. Upload generated cloud-function-aws-0.0.1-SNAPSHOT-native-zip.zip. Finally, test by providing JSON-style string value via ‘Test’ tab. For example {“name”:“john”}. The output should be “Hi JOHN!”

Check out those durations

Lambda execution durations screenshot

Try it out and let me know what you think in the comments below.

About the author

Hi, I'm Karl Kyck a cloud architect specialising in building sustainable serverless architectures on AWS.