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
Jan 20, 2026
JDBC vs ORM vs jOOQ: Choose the Right Java Database Tool

Still unsure what is the difference between JPA, Hibernate, JDBC, or jOOQ and when to use which? This video clarifies the entire Java database access stack with real, production-oriented examples. We start at the foundation, which is JDBC, a low-level API every other tool eventually relies on for database communication. Then, we go through the ORM concept, JPA as a specification of ORM, Hibernate as the implementation and extension of JPA, and Blaze Persistence as a powerful upgrade to JPA Criteria API. From there, we take a different path with jOOQ: a database-first, SQL-centric approach that provides type-safe queries and catches many SQL errors at compile time instead of runtime. You’ll see when raw JDBC makes sense for small, focused services, when Hibernate fits CRUD-heavy domains, and when jOOQ excels at complex reporting and analytics. We discuss real performance pitfalls such as N+1 queries and lazy loading, and show practical combination strategies like “JPA for CRUD, jOOQ for reports.” The goal is to equip you with clarity so that you can make informed architectural decisions based on domain complexity, query patterns, and long-term maintainability.

Videos
card image
Jan 13, 2026
Hibernate: Ditch or Double Down? When ORM Isn't Enough

Every Java team debates Hibernate at some point: productivity champion or performance liability? Both are right. This video shows you when to rely on Hibernate's ORM magic and when to drop down to SQL. We walk through production scenarios: domain models with many-to-many relations where Hibernate excels, analytical reports with window functions where JDBC dominates, and hybrid architectures that use both in the same Spring Boot codebase. You'll see real code examples: the N+1 query trap that kills performance, complex window functions and anti-joins that Hibernate can't handle, equals/hashCode pitfalls with lazy loading, and practical two-level caching strategies. We also explore how Hibernate works under the hood—translating HQL to database-specific SQL dialects, managing sessions and transactions through JDBC, implementing JPA specifications. The strategic insight: modern applications need both ORM convenience for transactional business logic and SQL precision for data-intensive analytics. Use Hibernate for CRUD and relationship management. Use SQL where ORM abstractions leak or performance demands direct control.

Further watching

Videos
card image
Feb 6, 2026
Backend Developer Roadmap 2026: What You Need to Know

Backend complexity keeps growing, and frameworks can't keep up. In 2026, knowing React or Django isn't enough. You need fundamentals that hold up when systems break, traffic spikes, or your architecture gets rewritten for the third time.I've been building production systems for 15 years. This roadmap covers three areas that separate people who know frameworks from people who can actually architect backend systems: data, architecture, and infrastructure. This is about how to think, not what tools to install.

Videos
card image
Jan 29, 2026
JDBC Connection Pools in Microservices. Why They Break Down (and What to Do Instead)

In this livestream, Catherine is joined by Rogerio Robetti, the founder of Open J Proxy, to discuss why traditional JDBC connection pools break down when teams migrate to microservices, and what is a more efficient and reliable approach to organizing database access with microservice architecture.

Videos
card image
Jan 27, 2026
Sizing JDBC Connection Pools for Real Production Load

Many production outages start with connection pool exhaustion. Your app waits seconds for connections while queries take milliseconds; yet, most teams run default settings that collapse under load. This video shows how to configure connection pools that survive real production traffic: sizing based on database limits and thread counts, setting timeouts that prevent cascading failures, and implementing an open source database proxy Open J Proxy for centralized connection management with virtual connection handles, client-side load balancing, and slow query segregation. For senior Java developers, DevOps engineers, and architects who need database performance that holds under pressure.