Micronaut vs Quarkus – Part IV: Going Native

One of the promises of both Micronaut and Quarkus is to make it easy to run microservices as native images. Native images promise faster startup times and a lower memory footprint compared to their plain jvm jar counterparts, making them ideal for command line apps and, above all, serverless functions.

Now, let’s see how easy it is to go native with the Micronaut and Quarkus microservices we built in our two previous blog posts. So grab a beer and tag along.

GraalVM

To bake our native images, we need GraalVM with native image tool installed.

There are several ways to install GraalVM depending, for one, on your operating system. I’m running Ubuntu and have SDKMAN installed. Installing GraalVM with native image tool for me required just these two commands:

sdk install 21.0.0.r11-grl for JDK 11
gu install native-image

For the proper steps needed for your OS, check the GraalVM docs here.

Micronaut

The original blog post was quite old, so I first took the liberty to upgrade it to the latest installment of Micronaut, i.e. version 2.4.0 (though 2.5.0 is already available as I’m finishing this blog post). Final code can be found here.

While trying to build the native image for the Micronaut microservice, I ran into two issues. First of all, my Ubuntu installation was missing an important library, leading to this error message:

It appears as though libz.a is missing. Please install it.

Second bump on the road that I hit, was this guy (when calling the GET beers/1.0 resource):

{
  "message": "Internal Server Error: Can't find a codec for class nl.terrax.tbrestmongodb.model.Beer.",
  "_links": {
    "self": {
      "href": "/beers/1.0",
      "templated": false
    }
  }
}

The stacktrace coupled to this error, read:

org.bson.codecs.configuration.CodecConfigurationException: Can't find a codec for class nl.terrax.tbrestmongodb.model.Beer

The only change I needed to make to fix this issue, was adding the @Introspect annotation on both model objects.

Now, there are two ways to build and run this application as a native image. One creates a Docker image, while the other creates an OS native executable. Let’s examine both.

docker

Creating the docker-native image is done like this:

mvn clean package -Dpackaging=docker-native

Now, before you spin up the image, make sure to create a docker network so the Micronaut container and MongoDB container can communicate with each other (remember that we used a Docker container for running MongoDB):

docker network create mn-network

Next, add the MongoDB container – running under the name of mongodb – to this network:

docker network connect mn-network mongodb

And finally, spin up the docker-native image in the same network, and you’re in business:

docker run -d -p 8080:8080 -e MONGO_HOST=mongodb
 --network mn-network --name mn-mongodb
 tb-rest-micronaut-mongodb:LATEST

Check docker images for the correct image name on your machine.

native image

Running the Micronaut service as a native image is even more simple.

Just build it:

mvn clean package -Dpackaging=native-image

And run it, to watch the beers flow:

./target/tb-rest-micronaut-mongodb

Quarkus

The Quarkus app, as it was built in Dirk’s blog post, needs even fewer adjustments. Final code can be found here.

docker

For the docker image to be able to communicate with the docker container running MongoDB, we also need to make sure both containers run in the same network (as we did in the Micronaut example), and the Quarkus service can point to the host running MongoDB.

This can be done by changing the connection-string property (in the application.properties file):

quarkus.mongodb.connection-string = mongodb://${MONGO_HOST:localhost}:${MONGO_PORT:27017}

This way we can set the MONGO_HOST environment variable when starting the service container.

We also need to add the container-image-docker container to the Quarkus setup:

./mvnw quarkus:add-extension -Dextensions="container-image-docker"

This will add the following dependency to the pom.xml:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-container-image-docker</artifactId>
</dependency>

Now we can build the image:

mvn clean package -Pnative -Dquarkus.container-image.build=true

And spin up the docker-native image in the same network the MongoDB container is running:

docker run -d -p 8080:8080 -e MONGO_HOST=mongodb
 --network mn-network --name qk-mongodb
 rphgoossens/mongodb-quickstart:1.0-SNAPSHOT

Check docker images for the correct image name on your machine.

native image

Again, building and running the native image is super easy. Build it:

mvn clean package -Pnative

And run it:

./target/mongodb-quickstart-1.0-SNAPSHOT-runner

Summary

In this post we saw how easy it was to run both our Micronaut and Quarkus microservices as native images. Both frameworks seem to support it out of the box.

Of course our services are not overtly complex. So there are no guarantees that you won’t be running into issues when trying to run your own services as native images, but for simple REST to MongoDB services you should have no problems.

References

GraalVM

Micronaut

Quarkus