Making sure applications run at peak performance and optimise memory consumption can be a constant challenge. Applications written in some languages and frameworks may have slowed performance due to the additional work required for these to set up execution environments and get applications running in a warm state.
Java is one of these languages, and this is especially true for applications based on frameworks that need to be warmed up, an example being SpringBoot. In this blog, we investigate native build for Java applications, and share knowledge on the performance improvements achievable and the practicality and suitability of this approach.
While in many cases start-up time is not a problem, one situation where it is known to be important is with serverless technology, including products such as AWS Lambda and GCP Cloud Functions. In the context of a serverless deployment – small programs are typically started, perform a task and shut down again when demand drops off. Here, the time to start from a cold state is material. AWS recently released StartSnap to resolve this issue for Java applications that run on AWS Lambda, but conceptually the challenge remains with other deployment options.
One emergent way to get Java programs to start quicker is to build them into native executables using a recent technology on the Java scene called GraalVM. Native executable build means producing runnable software that targets a platform architecture such as ARM64 or x86_64.
Spring has been actively working to make SpringBoot-based applications build well as native executables. Quarkus is a younger and lighter alternative to SpringBoot that has been able to build smoothly to native executable from the offset.
Simple Example Application
To demonstrate the technology, ClearPoint built a couple of simple applications using Java together with Quarkus, as well as SpringBoot. The startup time is, as expected, far reduced with the native executable.
Another desirable side-effect of the native executable is that it contains only the material which is necessary to run the application. For this reason, the resultant build product tends to be smaller than the distributed Java application together with a Java runtime environment.
Memory consumption is reduced as well because the native executable does not need to carry all of the metadata necessary to operate the Java virtual machine.
This sounds great, but there are downsides to this technology as well. Building to native takes longer than building a traditional Java build product.
Something we have not yet investigated but is a known behaviour is that once the application is warm, the regular HotSpot Java Virtual Machine execution can often outperform that of the native executable. This is because of its ability to dynamically change the executed machine code to suit the behaviour of the application; something the native executable cannot do.
Java applications also have a means of introspecting parts of itself as it runs. This functionality is called reflection. The native executable build procedure also has to be instructed to support some Java features around reflection which can be challenging in some cases as discussed below.
Building to native executable definitely brings into play some positives, but also has some drawbacks.
Reachability And Build Configuration
The native build compilation system uses a process of “reachability” to discover what does and what does not need to be included in the native build target. Reachability means that beginning from an initial class, it is possible to transitively reach another class via references. Any classes that are unable to be reached can be omitted from the native executable.
It is therefore clear how reflection, by and large, would not work well with reachability since the reflective lookup of classes, methods and fields is dynamic as the software operates. Resources such as images and JSON files are likewise referenced by textual names and it is not possible for the native executable to know what resources are and which are not used at compile time.
To incorporate aspects of reflection and resources into the native build, it is necessary to use build configuration files that specify to the native executable which resources and classes to explicitly include and what aspects of those classes to make usable with reflection.
Some open source libraries have these build configurations available and some don’t. Where they are necessary, these files can be time-consuming to assemble.
Non-Standard Libraries in an Older Application
Switching from the simple examples above, we also looked at an as-built, medium-complexity Java application, built with Java 17 and SpringBoot 3. The application is around 10 years old and depends on some less common open-source libraries that don’t have native build support out of the box today.
The process of implementing support for those libraries in the native build process was a more adventurous undertaking requiring intricate knowledge of the libraries’ workings.
It was possible to get the application’s native build to the point where it would start up, but there was still some work to go in order to get full functionality working. This highlights that for applications using standard frameworks and supported libraries, the path seems easier but if unsupported libraries are in play then it can be difficult.
Conclusion
Native executable is an interesting option for Java applications where fast start time, a smaller memory footprint and a smaller container image are of great importance. The drawbacks are the slower build time and build complexity. The technology relies on dependencies being ready for native image; if these are not ready then building a native executable can be a challenge. Having frameworks such as Helidon, Micronaut, Quarkus and SpringBoot able to play ball with native image is a big step forward and paves the way for realistically considering these techniques.
Watch the following video to find out more about GraalVM native executables. We are happy to discuss upgrade paths for those wanting to modernise existing systems utilising Java 17 – get in touch today.