Function-as-a-Service or Serverless is the most economical way to run code and use the cloud resources to the minimum. Serverless approach, runs the code, when a request is received. The code boots up, executes, handles the requests, and shuts down. Thus, utilizing the cloud resources to the optimum. This provides a highly, available, scalable architecture, at most optimum costs. However, Serverless architecture demands a faster boot, quicker execution and shutdown.
GraalVM native images (ahead of time) is the best runtime. GraalVM native images have a very small footprint, they are fast to boot & they come with embedded VM (Substrate VM).
I had blogged about GraalVM here. Please refer to the following blogs, for better understanding of the architecture of Graal VM and how it builds on top of Java Virtual Machine
Episode 1: “The Evolution” — Java JIT Hotspot & C2 compilers (the current episode…scroll down)
In these blogs, I will talk about how GraalVM embraces polyglot, providing interoperability between various programming languages. I will then cover how it extends from Hotspot, and provides faster execution, and smaller footprints with “Ahead-of-time” compilations & other optimisations
In this blog lets focus on building simple KG to Pounds converter function in Java. First we will build a serverless application with Java, and then later build it using GraalVM native image. We will then compare how fast and small GraalVM implementation is.
- Install docker (refer to https://www.docker.com/ for latest instructions)
- Install fn (refer to https://fnproject.io/ for latest instructions)
1. Starting fn daemon
Start the fn daemon server using
The fn server runs in docker, you can check that by running
docker ps The screenshot below shows, what I am able to see in my computer.
2. Generating the fn boiler plate code
Now we can generate the boiler plate code with
fn init --runtime java converterFunc
This creates a folder
converterFunc with all the boiler plate code
Let’s inspect what is inside that folder. You will see a
pom.xml & a
func.yml is the main manifest yaml file that has the key information about the the class that implements the function and the entrypoint. Lets inspect that
- name: The name of the function, we can see the name of the function that we specified in our command line
- version: Version of this function
- runtime: Java Virtual machine as the runtime
- build_image: The docker image that should be used to build the java code, in this case we see its JDK 11
- run_image: The docker image that should be used as a runtime. In this case it is JRE11
- cmd: This is the entry point, which is the ClassName:MethodName
fn has all the information that it needs in this yaml to build and run the function, when it is invoked.
Now let's look at the maven file (pom.xml).
We see the repository from where the fn dependencies are to be pulled
and the dependencies
src folder we will find
HelloFunction.java, which is the default boilerplate code that is generated by fn.
The code is very straight forward. It has a
handleRequest () method, which takes in the
String as a input and returns
String as an output. we can write our function logic in this method. This is the method that fn, calls when we invoke the function.
3. Writing our logic
Lets build our converter application. I am going to deploy it into the path
src/main/java/com/abvijay/converter , and the name of my Class is
The code is very straight forward. I am just expecting a kgs value in
String, converting that to
Double and calcualting pound value and returning that back as a
String. ( I did not write a lot of exception handling, to check for edge conditions, to keep it simple).
Now we need to update the
func.yaml to point to our new Class
Check the line number 7, That is changed to point to the new class and method.
4. Build & Deploy the serverless container to local docker
Functions are grouped into applications. an application can have multiple functions. That helps in grouping them, and managing them. So we need to create a
fn create app converter-app
Once the app is created, we can now deploy the app.
fn deploy --app converter-app --local
fn deploy command will build the code using maven, package it as a docker image, and deploy it to the local docker runtime. fn can also be used to deploy to cloud or k8s cluster directly.
Lets now use
docker images command to check if our image is built.
We can also use
fn inspect to get all the details about the function, this helps in discovery of the services.
fn inspect function converter-app converterfunc
5. Running and Testing
Now lets invoke the service, since our function expects a input argument in number, we can pass it using a echo command and pipe the output to
fn invoke to invoke our function
echo -n ‘10’ | fn invoke converter-app converterfunc
We can see the result coming from the function. Now lets run the same logic on GraalVM
6. Run on GraalVM, as a native-image
The base image for GraalVM is different, we use
fnproject/fn-java-native-init, as the base, and initialize our fn project with that
fn init --init-image fnproject/fn-java-native-init converterfuncongraalcd cnverterfuncongraal
This fn configuration works differently. It also generates a Dockerfile, with all the necessary docker build commands. This is a multi-stage docker build file. Lets inspect this dockerfile
- line 17: The image will be built using
- line 18: setting the working directory to /function
- line 19–23: Then the maven environment is configured.
- line 25–40: Using base image as
fnproject/fn-java-native, the GraalVM is configured and fn runtime is compiled. This is a very important step, this is what makes our serverless runtime faster and with smaller footprint.
- line 43–47: Using busybox:glibc (which is the minimal version of linux+glibc) base image the native images are copied.
- Line 48: is the function entry point. the func.yml in this way of building the serverless image has no information. fn will use dockerfile to perform the build (along with maven) and deploy the image to the repository
Now we need to change line 48 to point to our class. let's replace that with
CMD [ “com.abvijay.converter.ConverterFunction::handleRequest” ]
Another important configuration file, that we need to change
src/main/conf This json file has the manifest information about the class name and the method.
Let's change that to
now let’s create a new app and deploy this app and run and see
fn create app graal-converter-app
fn deploy --app graal-converter-app --local
echo -n '20' | fn invoke graal-converter-app converterfuncongraal
There you go, our code is now running on GraalVM. So what's the big deal. When I ran
docker images , I see the size of Java image as 223 MB and the GraalVM image is just 20MB. That is 10 times smaller footprint.
When I timed the function calls, Java function took around 700ms while GraalVM took around 460ms. That is almost 30% faster. For functions with more complex logic, the differences will be much more significant.
Java hotspot might catch up with this number, but that is provided the function runs longer, and the Just in time Compiler kicks in to optimize the code. Since most of the functions are expected to be quick and short running, it does not make sense to compare this JIT benchmarks.
There you go…I hope this was fun…ttyl :-)