jLuger.de - Native x86 Microservice in Java: The native executable

This is the third part of a series about developing a native x86 microservice in Java. And it is about making an x86 binary from a Java program.
As already mentioned I wanted to use GraalVM for this. GraalVM is a project that was based on OpenJDK 8. As there was already a ticket to port to a later version the version information is hopefully outdated when you read this lines. GrallVM is also a project with many goals. Go to their website to inform yourself about it. But I will only focus on the ability to use ahead of time compilation to create native executables.

To create a native executable the GraalVm provides a tool called native-image. The native-image command needs to get your program as a argument like the java command. The easiest way for this is to create a fat jar and call native-image -jar your-program-fat.jar. Unfortunately the chances to get an error message instead of a working executable are pretty high. That's because native-image supports no reflection (except with some help) and also forbids other constructs.

E.g. I had to switch from H2 db to Hsqldb because H2 db uses " a direct/mapped ByteBuffer" and "A direct ByteBufer has a pointer to unmanaged C memory, ...". Hsqldb doesn't use such a ByteBuffer and thus doesn't break the build.

As already mentioned native-image doesn't support reflection out of the box. While I could rewrite the usage of Jdbi to not use reflection neither GSON nor Hsqldb could be changed to work without it. Fortunately reflection is so widely used that they had created a solution for this problem. With the parameter -H:ReflectionConfigurationFiles you can provide a json file. In the json file you can specify the class, the methods of the class and when needed the fields that should be available for reflection.
The bad part of this is that you get only one reflection error after the previous one was fixed. My 5,5MB fat jar took over 2 minutes to compile. So the cycle was fix, wait 2 minutes for the compile and then fix the next error and so on.
Be also warned that I've read the the compile time is exponentional to the jar size. So be warned when trying larger frameworks.

After the native-image created an executable I had to find out that Hsqldb contains some *.properties and *.sql files it needed to initialize. By default they aren't included into the executable. To solve this I had to use -H:IncludeResources=.*.properties|.*sql.

During my many trials I've found out that native-image tries to use a server by default. That server performed sometimes unwanted caching. To get rid of it I've used the --no-server option. I've noticed no longer compilation time but my sanity level increased immensely.

native-image also has a --static option. This creates an executable that references no shared libraries. Pretty handy when putting things into containers.
Another strange part of the static option is that the generated executable gets smaller. Normally the files get larger but not in this case. The executable without the static file is 34MB while the one with the static option is 29MB.
Also the size of the executable is pretty large it starts in under 100ms. That's the startup time you want when millions of people go to your website after a successful commercial on TV.


This post is part of a series: