You've built and tested your service. How can we deploy it into production? One approach is to create an assembled JAR containing the service with all its dependencies. We can then execute it via java -jar. Another approach would be to create a native image binary via GraalVM. We will give each of these as examples below.

Assembled JAR

As an example of building an assembled JAR we can use SBT with the sbt-assembly plugin. Simliar approaches should exist for other build tools. Consult their documentation. For SBT we find the plugin

For a simple project we should be able to add the sbt-assembly plugin to project/plugins.sbt and then run the assembly task. We might need to use a merge strategy if some of our dependencies have conflicting artifacts. Below is a simple example, but following the documentation will give more specific configuration examples.

in project/plugins.sbt:

addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "sbt-assembly-version")

Then as long as we have a main method in our project:

sbt assembly

should build our "fat" assembly jar. We should see the path of the assembly from the sbt log. For example target/scala-2.13/project-assembly-0.0.1-SNAPSHOT.jar

Finally we can execute the jar with

java -jar /

At this point you should see your http4s server start up. Regarding the artifact, it might be a good idea to place it inside a docker container or create a service script via a systemd unit or similar.

Graal Native Image

We provide an outline for building a static native image below. Furthermore the http4s giter8 template can be used to build a native image in conjunction with this guide.

Why would we create such an image? Some advantages might be faster startup times or less memory usage than the JVM.

Install GraalVM and Native Image plugin

The first step is to install the core GraalVM and native-image plugin. The core GraalVM release might be thought of as a replacement for the JVM. The native-image plugin is required to create the binary. The community edition builds are free.

After installing the core GraalVM you should be able to use it as a JDK. For example you might set JAVA_HOME to point to your Graal version. Otherwise, your build platform might allow you to select the Java library to build against Graal.

export JAVA_HOME=/path/to/graalVM


java --version

should return something like

openjdk 11.0.6 2020-01-14
OpenJDK Runtime Environment GraalVM CE 20.0.0 (build 11.0.6+9-jvmci-20.0-b02)
OpenJDK 64-Bit Server VM GraalVM CE 20.0.0 (build 11.0.6+9-jvmci-20.0-b02, mixed mode, sharing)

(Optional) Get or build a muslC bundle required to build a static image.

Note: Static images aren't supported in MacOS or Windows . If building for those platforms skip this step

To create a truly static native image we need to use muslC . Instructions and an example bundle are provided here. For the sake of our example, we can download the resulting bundle for our build. We will need to use the path to the unpacked bundle as an argument to build the image.

wget -O - | tar -xz

META-INF resources for reflection

We need META-INF resources whenever we use reflection. Reflection isn't used in http4s, though it is used by some logging implementations. We've provided a native image example in the http4s giter8 template

Build an assembled jar using GraalVM

After installing the above dependencies you should build an assembled JAR. We can again use sbt assembly or your favorite build tool / plugin to create the assembled jar. The important thing is that we should be using the GraalVM version of Java to do so.

Create the native image with the assembled JAR

After we have built the assembled JAR containing all our Java dependencies, we use that JAR to build our native image. In the command below we need to replace the muslC and assembly jar paths with the appropriate locations.

Note: Mac and Windows platforms do not support build static images. Remove --static and -H:UseMuslC="/" when building for those platforms.

native-image --static -H:+ReportExceptionStackTraces -H:UseMuslC="/" --allow-incomplete-classpath --no-fallback --initialize-at-build-time --enable-http --enable-https --enable-all-security-services --verbose -jar "./" projectBinaryImage

A breakout for the command parameters (image generation options) :

-H:UseMuslC to fix DNS and related segfaults and build a true static image that doesn't depend on linked libraries.

--initialize-at-build-time again is related to building the static image. Again we lose performance if we instead do this at runtime.

--enable-http, --enable-https should correspond to related protocols

--enable-all-security-services will likely be required to make or receive https requests.

Additional image generation options are found in the native image reference

Execute the native image

Finally we should be able to execute our output projectBinaryImage


At this point we may want to package the binary in a docker container, integrate the image generation to a greater build process, create init scripts via systemd, etc.

Why static?

As an alternative to executing via the JVM, GraalVM's native-image allows us to execute as a native binary. For example, in Linux environments this might be known as an ELF format. There are a number of ways to generate a native image, be it dynamic or static.

A dynamic image is less portable because it depends on shared library files on each Linux host. Then similar to creating an assembly JAR containing all our Java dependencies, when we build a static native image, we build a more portable assembly including all the dependencies to run our binary across multiple platforms. For example we could expect a static ELF-64 binary to work across multiple linux distributions of different versions for the same architecture.