How to build a Graal native-image from a Scala app

Niklas Klein
3 min readSep 11, 2020

Graal is currently on everyone’s lips and for good reason: as developers that deploy their software on top of the JVM, we are used to long startup times and high memory requirements. Graal’s native-image promises to solve both of these problems, enabling a new and exciting use case: real time serverless instances on platforms such as “Google Cloud Run”.

Prerequisites

In this tutorial we will take a simple http4s server application and walk through the Graal native-image configuration and build process. Start by checking out the sample code from the GitHub project taig/scala-graal.

Building the Scala app

At first we have to generate a far jar from our Scala app. This is done via sbt-assembly. The dependency has already been added to the project/plugins.sbt file, so proceed by kicking it off via sbt assembly which will generate ./target/scala-2.13/scala-graal-assembly-0.1.0-SNAPSHOT.jar.

Building the native-image

The Dockerfile in docker/build has all it takes to turn the jar file from the previous step into a Graal native-image. Simply run the command below from the project root (this might take a while).

If you are on macOS, make sure your Docker daemon has at least 4GB of RAM assigned.

docker run --rm -it -v $PWD:/app/ $(docker build -f docker/build -q .)

The docker/build image is based on oracle/graalvm-ce:20.1.0-java11, I couldn’t get it to work with the more recent 20.2.0 yet.

Building a Graal native-image relies heavily on configuration. You can find the respective configuration at ./src/main/resources/META-INF/native-image/io.taig/scala-graal but you will see that the files are mostly empty. This is due to the simplicity of this example project. A real world project with plenty of dependencies can be incredibly difficult or even impossible to configure. I generally tend to struggle with the Google Cloud Java libraries, as they heavily rely on reflection. I didn’t manage to get doobie working (probably due to JDBC), but skunk works just fine. Be warned and prepared for painful and tedious trial and error sessions.

Running the executable

Now the executable should already be waiting for you in the project root directory. The easiest way to execute it is via a docker scratch container with the command below.

docker run --init --rm -p 8080:8080 $(docker build -f docker/deploy -q .)

Make sure it works via a simple curl request:

$ curl http://localhost:8080/
> ¡Hola mundo!

To get a better feeling for the startup time, you may want to build the deploy image separately:

$ docker build --tag scala-graal-deploy -f docker/deploy .
$ docker run --init --rm -p 8080:8080 scala-graal-deploy

Which should start the server instantaneously compared to running on the JVM, which takes slightly longer.

java -jar target/scala-2.13/scala-graal-assembly-0.1.0-SNAPSHOT.jar

Conclusion

Graal native-image is an exciting technology that makes JVM-based languages an interesting candidate in the world of serverless cloud computing. However, the configuration process can be incredibly tedious and does not warrant to Graal native-image all the things just yet. Unfortunately it’s mostly the Java ecosystem which is to blame here. Java libraries are omnipresent but still tend to rely heavily on reflection.

Further reading

--

--