Turn your 4-second Java startup into 0.5 seconds with GraalVM Native Image

 

Transcript:

GraalVM Native Image turns your Java apps into executables that start in under a second. In this video, we'll see what Native Image is, why it matters in the cloud, compare available distributions and see what it takes to use GraalVM Native Image with production rage. Spring Boot Applications. No Petclinic, only the real deal. This video combines both theory and practice, so jump to the section you need.

Java is like a marathon runner: it keeps pace and adapts performance based on the changing loads. But in the cloud we need a sprinter in certain scenarios: like Microservices that start in a chain delay system readiness. Scaling for sudden load spikes takes too long. So lost requests, lost customers. False stars in serverless cost money. GraalVM Native Image helps to turn Java into a sprinter! Native Image compiles your Java app into an executable for a specific operating system and architecture. It bundles only the code and libraries the application need and doesn't require JVM to run this native executable starts up almost instantly and doesn't have the warmup stage. So how exactly does Native Image work? Traditionally, the source code is compiled into Java bite code. We get the JAR file, and then when we run this jar file. The JVM interprets Java bytecode into machine code and optimizes it for better performance at runtime. So after the application starts, this is called the just in time compilation Native Image works differently. It compiles byte code into machine code. Ahead of time. So at build time, the native image compiler performs the static analysis of the code to determine which program elements are required by the application. It can also initialize some classes and write some objects onto the native image here. It all happens under the closed world assumption. It means that the compiler assumes that all program elements that will be required at runtime are reachable at build time only. These elements will be included into the native image. After the analysis, the compiler built a native image. The resulting binary is fully compiled and optimized for performance.

Several vendors provide raw VM native image. All these distributions are available for Linux and MacOS X86 and Arch 64 and Windows X86. Commercial support is available for all of them, excluding community edition. The key differences between them include licensing, support, roadmap, available garbage collectors. And additional improvements. Let's look at all of them. Raw and Community Edition is distributed under the GPL version 2 with a class path exception. It is based on open JDK and includes basic garbage collectors. EpsylonGC and SerialGC. Quarterly updates are available for the latest JDK release. Oracle GraalVM is provided by Oracle and is based on Oracle JDK. It is distributed under the GraalVM free terms and conditions, including license for early adopters. It includes basic garbage collectors plus G one C and also some improvements such as profile guided optimizations. Free quarterly updates are available for the latest non LTS JDK version and the latest. LTS JDK version. Liberica Native Image Kit is provided by BellSoft. It is based on raw VM Community Edition and Liberica JDK, but comes with a few extras barrel, which you see a bundle with Java FX and enterprise support. It is also used by default in Pato build packs for spring quarterly. Updates are available for the latest non LTS JDK version. And several LTS JDK versions, currently 17 and 21. Mandrel is a Red Hat build of GraalVM focused on corcus. It is based on open JDK and includes the basic garbage collectors. Free quarterly updates are available for the latest non-LTS JDK version, and the latest LTS JDK version glue on builds of GraalVM are tailored for JavaFX desktop and mobile applications. They target iOS and Android. In addition to desktop, but the builds haven't received any updates since September 2024. You can use it by adding GluonFX plugin to your project.

Let's look briefly at what you should expect when dealing with native image. So this technology might not be suitable for services. That's get updated constantly. The build process is very resource demanding. Note that GitHub hosted runners are often too small, so the GraalVM build process may fail with the out of the memory error. So you may need larger or self-hosted runners. Dynamic features like uh, reflection or resources must be declared. Frameworks like spring co and micro node generate metadata automatically. In other cases, you may need to use the tracing agent tool to collect the metadata. The startup is better, but the performance of long-running apps may lag behind JVM because Native Image lacks dynamic performance optimization and has a limited choice of garbage collectors. You can build native images locally using Gradle or Maven plugin in a Docker container with build packs or in CI. Major frameworks support GraalVM native image out-of-the-box. So in the best case scenario, building a native image is just a matter of one command. In other cases, while you may have to tame a dragon or two. But enough talk, let's see some action! First, let's see how we can build a native image locally. Download and install the preferred GraalVM distribution.

The easiest way is to do it through SDK Man. Simply run SDK list Java, and you'll see all the available options. Here's the GLION, GrowLVM native image, GraalVM Community Edition, GraalVM Oracle, Liberica Native Image Kit and mandrel. Choose the build and run SDK-installed Java and the version. We're going to look at several applications first. The newer watch app, newer watch application is based on JDK 24 and Spring Boot 3.5. It uses Spring Security, spring data MongoDB for the persistence layer and also Vardin as a front-end framework. New Watch is a Cyberpunk themed application. It lists civilians with implants installed into them. You can browse the civilians. Edit them, add implants, and also you can monitor the implant logs for the specific date. This application starts in almost 4 seconds. Let's see how we can improve that. So first point, the Java Home onto the GraalVM distribution. In some very, very simple cases, you can simply use the native image tool in the beam subdirectory of the distribution to build a native image out of the executable jar. But in many cases, so when you build a Spring Boot application, these applications are more complex than simple. HelloWorld AppCDS. So it is better to use the Maven or Gradle plugin. The plugin for GraalVM calls the same native image tool under the hood, but adds a lot of options and flags so that you can avoid common issues when trying to build the native manually. First of all, let's specify in our Maven plugin that you want to build an executable jar file and specify the main class. You can also enable the ProcessAOT goal. This Spring Boot feature was built to support GradVM native image, but it can also be used with AOT cache, for example, or AppCDS or simply with your application to boost startup time a little bit. Okay, now we need to add the GraalVM build tools plugin. The easiest way to do that is to add a profile with ID native. Here in this plugin, you can specify various configuration options that you need. For instance, you can specify the image name, the output directory, and the build arguments. We are going to talk a little bit more about that later. Here we also specify the entry point to the main application class. After that, building a native image is really just a matter of one command. Maven, p native, native compile. But as I use vaadin, I also need to specify another profile, which is a production profile to build the front end for production. After that, you can start the native, uh. Image. Just like you start any other application, you don't need the JVM to run it, and this native executable starts in half a second.

You can also build a native image inside the Docker container. For this purpose, you will need to to use the GraalVM distribution as a base image. To build a native image and then you transfer it into the final image with only Linux because again, it doesn't need a JVM to run. So in this case I'm using Liberica Native Image Kit container based on Liberica Native Image Kit and Alpaquita Linux. I have to add a couple of packages for building vaadin in the front-end, but this is strictly because of the vaadin used in my application, you don't need to do that with yours. And then we simply run the same command that we run when we built a native image locally, Maven P Native, native compile. Then you take this executable and transfer it into the base image with Linux. Sounds too easy. Well, in some cases, if you use a musl based Linux distribution, such as Alpine or Alpaquita, you may run into the issue in trying to build a native image. Like this one. This is a linking issue. It means that musl library lacks necessary tools like libscdc++. Not always, but sometimes you may encounter this issue. In this case you have two options. You can add necessary packages from the repository, or you can switch to a glibc-based distribution. For instance, if I had this problem with the neuro watch application, I would simply use another base image, Liberica Native Image Kit container based on glibc, and then Alpakita Linux with glibc, and that's it. In the case of Alpine, there is no glibc available, so you may have to add the necessary Packages. You can also build a native image using build packs. That's just one command, maven spring would build image P native.

Okay, we didn't specify almost no options, but you can configure your native image built as you see fit. For instance, you can run native help and then you will see a list of available options. You can specify these options in the command line. Or in the plugin. So here you can see the AT modules option module, path Option diagnostics Mode. To Enable Diagnostics, enable HTTPS or enable HTTP. Enable Monitoring to Enable Monitoring with JFR or other similar tools enable native access. You can specify the garbage collector. So here in this case, as I'm using a Libc kit, the available options are absalon parallel you see and serial. You can specify glasses and packages that should be initialized at build time or at run type. Also, you can select the libc implementation like musl to libc or bionic. You can specify the number of threats to use during the native hemorrhage generation. You can build a fully static native image that doesn't require even the Linux to run. You can also control code optimizations, for example. You can optimize the build for fastest build time. You can enable basic optimizations, advanced optimizations. Or even all optimizations for the best performance you can generate debugging information and a lot more. So run this command and study the possible options. The options can be specified in the plugin. So for instance, let's specify a garbage collector in the maiden plugin. I'm sensing frustration in the air. Catherine, promised no petclinic like experience with her tales of how hard it is. To introduce native image into the project. Don't worry, I have something up my sleeve. The truth is, is that I wanted to show you that in some cases, your service, even the production ready service, can be turned into native image without much hassle. But let's look at another application. It is a chat application. That integrates AI under the hood. It consists of two microservices, bo assistant and chat API. The application is based on JDK 21, the chat, API uses MongoDB for persistence layer Spring Security, JTE for front end. And some web socket client libraries. Bot assistant uses web flux Spring ai, spring Security readies for caching the AI responses, and also JTE for front end. Okay, chat API didn't need any twisting. It was built and run successfully with a simple command Maven. P Native Need to compile. With bot assistant, well, some surprises awaited us simply running P native. Native compile. Resulted in this error. Glasses says that should be initialized at runtime. Got initialized during build time. We didn't see this error with the first application neuro watch because it was based on G DK 24. And the latest native image option. So this error is solved under the hood, but as we use JDK 21 in this application, we need to solve for the issue manually. In this case, we need to add the strict image hip option. Okay, so we added to this option, let's run the command again. While the image was. Built successfully. But when we started, at some point it exits with another error.

So apparently the JFR module is missing, but the native image needs it for some reason, it turned out that when we create a connection to Redis, spring creates a custom JFR event. Well, lessons learned. Let's enable GFR. Let's add another argument. Enable monitoring GFR and build the image again. Okay, great. Now the image was built and run successfully, and this is a classic case of interdependent Services Chat. API service waits until the bot assistant service has started and is healthy, and then it starts. In this case, the whole system starts in almost seven seconds, but with native image. The system starts in less than a second. The native image compiler does a great job at collecting the metadata on the usage of dynamic features, but it cannot exhaustively predict all cases of genomism. So in some cases, you may need to run the tracing agent tool to collect the metadata before building the image. Let's see how we can do that. You can enable tracing agent in the plugin, or you can run it in the command line. Let's look at the second option. You need the agent sleep native image agent option, and also you need to specify the output directory for the metadata files. We use Java in this case, not the native image tool and we run our JAR file with the tracing agent enabled. You can also use the config merge, dear, if you already have a directory with the agent data, but you have introduced some changes. It's your application and you need to refresh the data so we don't have to run tracing agent. Again and collect the metadata from scratch. We can just add the new data to the existing. The application will start and you'll have to run it through all possible execution paths to gather all the cases of genomism that are used by the application. Then after you exit the application, the metadata will be generated automatically as JSON files. And then you specify the path to the metadata directory in the plugin. After that, you can run your command for building the nature of image as usual. Another way of running tracing agent is to launch the test. With the tracing agent enabled, so Maven P Native the agent true test, and then the metadata will be collected while the tests run. In this video, we looked at GraalVM native image. We looked into theory and set it loose on a couple of Spring Boot applications.

Summary

In this video, GraalVM Native Image is introduced as a way to turn Java applications into fast-starting executables without requiring the JVM. This is especially valuable for cloud-native use cases such as microservices and serverless, where quick startup and scaling are critical. The video compares different GraalVM distributions (Community, Oracle, Liberica, Mandrel, Gluon) and shows how to build native images for Spring Boot applications using Maven, Gradle, Docker, or buildpacks. It also covers common challenges like class initialization, JFR support, and metadata generation with the tracing agent.

About Catherine

Java developer passionate about Spring Boot. Writer. Developer Advocate at BellSoft

Social Media

Videos
card image
Aug 6, 2025
GraalVM for Java Developers: The Ultimate Beginner’s Guide

What is GraalVM and how can it improve your Java applications? In just 10 minutes, this video explains the three main components of GraalVM — the JIT compiler, Native Image, and Polyglot API. Learn how to boost performance, reduce startup time, and combine multiple languages in one app. Whether you’re building microservices, serverless apps, or just exploring modern JVM tooling, this is your quick-start guide to GraalVM.

Videos
card image
Jul 15, 2025
Java Downgrade Challenge: From JDK 8 to 1.1 (Part 2)

In Part 2 of the Java Downgrade Challenge, we continue our journey — now from Java 8 all the way to Java 1.1. No streams, no lambdas, no generics, no collections — and at one point, we even boot up Windows 98. If you thought Part 1 was painful, this one unwinds Java history line by line. By the end, the familiar Java from today will be almost gone.

Further watching

Videos
card image
Aug 27, 2025
Buildpacks for Spring Boot

Buildpacks for Spring Boot: no Dockerfiles, no hassle — just production-ready container images in one command. Tired of maintaining Dockerfiles? In this tutorial, you’ll learn how to use buildpacks to create optimized Spring Boot containers — fast, secure, and cloud-ready — with just one command. We’ll show what happens under the hood: automatic dependency detection, layered image creation, memory tuning, SBOM generation, and how to tweak builds with just a few plugin options. Need faster startup, smaller image size, or JFR monitoring? Buildpacks can handle it — and we’ll show you how.

Videos
card image
Aug 20, 2025
Flyway in Spring Boot: Step-by-Step tutorial with Maven

Learn how to use Flyway in Spring Boot with Maven for smooth and reliable database migrations. In this hands-on tutorial, we cover everything from setting up PostgreSQL in Docker, configuring Flyway in your application, writing versioned and repeatable migrations, to using Flyway in CI/CD pipelines with GitHub Actions. Whether you’re new to Flyway or want to master schema version control in Spring Boot, this video will guide you step by step.

Videos
card image
Aug 14, 2025
Stop Using DTOs – A Cleaner Way for Your Java APIs

Still creating DTOs for every API in your Spring Boot project? You might be overcomplicating things. In this video, we show why DTOs aren’t always necessary and how to replace them with @JsonIgnore, @JsonView, and Jackson Mixins. You’ll see real examples of hiding sensitive fields, creating role-based views, and cutting boilerplate — all while keeping your API safe, clean, and easy to maintain.