GraalVM and Micronaut – Hello WorldReading Time: 6 minutes
The GraalVM is one of those technologies that are getting interest in the Cloud Native world during the last couple of years. It comes to solve some key challenges when we have to develop services into the Cloud. Moreover, and keeping the context about fast and cloud-oriented software development, we have the modern Java framework Micronaut, which is also very hot nowadays.
In this article, we will perform the Hello World for a Micronaut application using the GraalVM, and we will analyze the initial lessons learned for considering this GraalVM technology into production environments. Engage!
GraalVM & Micronaut
I will not bother you too much on this article talking about these technologies, since there is a lot of literature on the internet for both of them, but let’s dedicate a couple of paragraphs for each of them.
GraalVM® has its roots in the Maxine Virtual Machine project at Sun Microsystems Laboratories (now Oracle Labs). The goal was to write a Java virtual machine in Java itself, hoping to free the development from the problems of developing in C++, particularly manual memory management, and benefit from meta-circular optimizations. [Wikipedia]
GraalVM comes to solve some key challenges in the Cloud world, such as the performance of Java virtual machine-based languages, to match the performance of native languages, or reduce the startup time of JVM-based applications by compiling them ahead-of-time with GraalVM Native Image technology.
The Micronaut® framework is a modern, open-source, JVM-based, full-stack toolkit for building modular, easily testable microservices and serverless applications. On their web page, they claim that your application startup time and memory consumption aren’t bound to the size of your codebase, resulting in a monumental leap in startup time, blazing fast throughput, and a minimal memory footprint.
From my experience, it’s true that both the memory footprint and the startup time are reduced using Micronaut framework, in comparison with other Java frameworks. Also, the number of integrations with third-party vendors, libraries and systems, make this framework a technology to take into consideration.
Said this, let’s get dirty.
Preparing the environment
I’m assuming you already installed the Micronaut Framework on your system. If not, please follow up here.
Now, install GraalVM for Java 11.
$ sdk install java 21.2.0.r11-grl
Then, install Native Image component.
$ gu install native-image
I can perform these installations easily on my laptop, not a problem. However, if I think from a company environment perspective, installing these tools into my Jenkins workers, maybe it’s not so straightforward. They represent another dependency to include and maintain. You have to either roll out an installation across your Jenkins workers farm or create new special ones (somebody said snowflake?) with this toolset. Nevertheless, I think we can agree that this is not a major issue.
At this point, we have the main foundation for being able to build a GraalVM binary. Let’s continue to the Micronaut Hello World.
Create Hello World project
I’m following the very well-known CREATING YOUR FIRST MICRONAUT GRAAL APPLICATION tutorial.
$ mn create-app --features=graalvm com.marcosflobo.graalvmtest --build=gradle --lang=java
Ready to fly. You can now open your preferred IDE and load the application. In my case, I’m not implementing any logic at all, just the Netty running to keep the application alive because, at this point for me, create a GraalVM-based application is the important part.
$ ./gradlew run will build and start the application in a few seconds. This was the easy-peasy part. Now, let’s build this application into a Native Image binary.
Creating the Native Image
We arrived at the important part of this article, in my opinion. In the tutorial, they indicate that by just running the
$ ./gradlew nativeImage task you will get the binary ready. In my case, I got this error.
$ ./gradlew nativeImage > Task :nativeImage [application:14945] classlist: 4,694.73 ms, 1.19 GB [application:14945] setup: 407.53 ms, 1.19 GB Error: Incompatible change of initialization policy for com.sun.jndi.dns.DnsClient: trying to change RUN_TIME from jar:file:///home/marcosflobo/.gradle/caches/modules-2/files-2.1/io.micronaut/micronaut-http-netty/2.1.2/77f31e4843aa87e29457ff4434fc40746653915e/micronaut-http-netty-2.1.2.jar!/META-INF/native-image/io.micronaut/netty/native-image.properties with 'com.sun.jndi.dns.DnsClient' to RERUN Contains Random references, therefore can't be included in the image heap. Error: Use -H:+ReportExceptionStackTraces to print stacktrace of underlying exception [application:14945] [total]: 5,145.81 ms, 1.19 GB # Printing build artifacts to: /home/marcosflobo/development/marcosflobo/graalvmtest/build/native-image/application.build_artifacts.txt Error: Image build request failed with exit status 1 > Task :nativeImage FAILED FAILURE: Build failed with an exception. * What went wrong: Execution failed for task ':nativeImage'. > Process 'command 'native-image'' finished with non-zero exit value 1 * Try: Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights. * Get more help at https://help.gradle.org BUILD FAILED in 7s 3 actionable tasks: 1 executed, 2 up-to-date
Here is the first lesson learned. The Micronaut version matters. My project was using version 2.1.2. I had to upgrade it to 2.5.0 just to make the command run. For me, that situation is a possible symptom that we might be in front of a technology that, at the minute, is still tricky and maybe delicate to be used in production environments. Once I’ve changed the version, the Native Image build started properly.
The build of the Native Image took 4 minutes, in a Ubuntu 20.04-based laptop with an Intel Core I7 (4 cores) of power. During the building time, the 4 cores were running at 100%. My laptop was flying.
On one hand, I’m not sure if, in a daily-basis development time, I can wait 4 minutes for building and testing my binaries. When we are implementing software, we really need speed for getting the output, either the result of the tests or the binary. The 4 minutes lapse maybe is too much.
On the other hand, we have the resources needed for this build. Four cores at 100% during the building time. Many of us have some dynamic and on-demand Jenkins workers, for building your artifacts, in Cloud provider. The resources (CPU, memory) needed to build these Native Image binary could bring an increment on your next bill.
Fast startup time is a fact
The resultant binary can run as
$ ./build/native-image/application __ __ _ _ | \/ (_) ___ _ __ ___ _ __ __ _ _ _| |_ | |\/| | |/ __| '__/ _ \| '_ \ / _` | | | | __| | | | | | (__| | | (_) | | | | (_| | |_| | |_ |_| |_|_|\___|_| \___/|_| |_|\__,_|\__,_|\__| Micronaut (v2.5.0) 14:03:27.161 [main] INFO io.micronaut.runtime.Micronaut - Startup completed in 94ms. Server Running: http://localhost:8080
When I run the app using
$ ./gradlew run it takes 1.2 seconds on startup time, which is okayish (these days). Running the Native Image binary takes 94 milliseconds only, which is really fast (even for these days). Please note that the reduction of startup time is one of the main goals of this technology. Of course, this application does nothing, so it would be worth it to make another one more complex, with more libraries, to see the impact on the startup time.
Ok, this was cool and nice, as a Hello World is supposed to be but, what about running a GraalVM application as a Docker container? or within a Kubernetes Pod? is the performance still so great? Are there some payed-products that squeeze the GraalVM even more to take all its potential?
This is something that will take in another post.
Mission accomplished!. We have our minimal Micronaut application running with a GraalVM Native Image. You can find the code on this GitHub repository.
Just for a really small Hello World, I faced an unexpected problem regarding the versioning of my framework, which was not expected on my end. For me, this could indicate (kind of) a code smell, not in terms of the quality of the service, of course, but in terms of maturity for agile production environments. It is true, though, that Iván López and Sergio del Amo (thank you guys, as usual) really indicate that the version of the framework to be used for that tutorial is not 2.1.2.
In general, my feelings about GraalVM are that we are in front of a great technology that will drive us one step forward in improving our cloud services. Micronaut framework looks like a good friend of GraalVM too. I think both technologies are worth it for a controlled POC in a production environment.
UPDATE 2023/01/15: Thanks to Sergio del Amo (thanks mate!), there is a new version of the POC using Micronaut 3.8.0. So, the code for this article can be found here and the same but with Micronaut 3.8.0 can be found here. Follow the conversation on Twitter.