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
Dec 12, 2025
Will AI Replace Developers? A Vibe Coding Reality Check 2025

Can AI replace software engineers? ChatGPT, Copilot, and LLM-powered vibe coding tools promise to automate development—but after testing them against 17 years of production experience, the answer is more nuanced than the hype suggests. Full project generation produces over-engineered code that's hard to refactor. AI assistants excel at boilerplate but fail at business logic. MCP servers solve hallucination problems but create context overload. Meanwhile, DevOps automation actually works. This breakdown separates AI capabilities from marketing promises—essential for teams integrating LLMs and copilots without compromising code quality or architectural decisions.

Videos
card image
Dec 12, 2025
JRush | Container Essentials: Fast Builds, Secure Images, Zero Vulnerabilities

Web-conference for Java developers focused on hands-on strategies for building high-performance containers, eliminating CVEs, and detecting security issues before production.

Further watching

Videos
card image
Dec 30, 2025
Java in 2025: LTS Release, AI on JVM, Framework Modernization

Java in 2025 isn't about headline features, it's about how production systems changed under the hood. While release notes focus on individual JEPs, the real story is how the platform, frameworks, and tooling evolved to improve stability, performance, and long-term maintainability. In this video, we look at Java from a production perspective. What does Java 25 LTS mean for teams planning to upgrade? How are memory efficiency, startup time, and observability getting better? Why do changes like Scoped Values and AOT optimizations matter beyond benchmarks? We also cover the broader ecosystem: Spring Boot 4 and Framework 7, AI on the JVM with Spring AI and LangChain4j, Kotlin's growing role in backend systems, and tooling updates that make upgrades easier. Finally, we touch on container hardening and why runtime and supply-chain decisions matter just as much as language features.

Videos
card image
Dec 24, 2025
I Solved Advent of Code 2025 in Kotlin: Here's How It Went

Every year, Advent of Code spawns thousands of solutions — but few engineers step back to see the bigger picture. This is a complete walkthrough of all 12 days from 2025, focused on engineering patterns rather than puzzle statements. We cover scalable techniques: interval math without brute force, dynamic programming, graph algorithms (JGraphT), geometry with Java AWT Polygon, and optimization problems that need constraint solvers like ojAlgo. You'll see how Java and Kotlin handle real constraints, how visualizations validate assumptions, and when to reach for libraries instead of writing everything from scratch. If you love puzzles, programming—or both—and maybe want to learn how to solve them on the JVM, this is for you.

Videos
card image
Dec 18, 2025
Java 26 Preview: New JEPs and What They Mean for You

Java 26 is the next feature release that brings features for enhanced performance, security, and developer experience. This video discusses the upcoming JDK 26 release, highlighting ten JEPs including JEP 500. JEP 500 focuses on preparing developers for future restrictions on mutating final fields in Java, emphasizing their role in maintaining immutable state. This is crucial for robust programming and understanding the nuances of mutable vs immutable data, especially concerning an immutable class in java. We also touch upon the broader implications for functional programming in Java.