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
Try it out and let me know what you think in the comments below.