I’ve spent a lot of time watching my services take 30 seconds to start. I’ve seen memory usage balloon for simple tasks. In a world where cloud costs and user patience are limited, this feels wasteful. It’s what pushed me to look beyond the traditional JVM. I found a path that leads to applications starting in less than a second, using a fraction of the memory. That path is building native executables with Spring Boot 3 and GraalVM. Let me show you what I’ve learned.
First, we need to understand the core idea. GraalVM Native Image doesn’t run your Java code on a standard Java Virtual Machine. Instead, it compiles everything ahead of time into a standalone binary. This binary includes your application, the necessary libraries, and a trimmed-down runtime. There’s no just-in-time compilation phase at startup. The code is ready to run as machine code from the very first millisecond.
This approach comes with a significant requirement. The compiler needs to know, at build time, every piece of your application that will ever be used. It must see all the classes, methods, and resources your code might touch. This is very different from the dynamic, just-in-time class loading we’re used to on the JVM. How do we tell it about things like database drivers or JSON mappers that use reflection?
Spring Boot 3 provides the answer. It comes with a dedicated AOT (Ahead-Of-Time) processing engine. During your build, this engine analyzes your Spring application. It figures out what beans will be created, what configurations are active, and what dynamic features you use. Then, it generates the configuration files that the GraalVM compiler needs to successfully build a native image.
Let’s set up a project. You can start from start.spring.io and add the “GraalVM Native Support” dependency. For an existing project, you need to add the Native Build Tools plugin. Here is a basic Maven configuration.
<build>
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
With Gradle, you simply apply the plugin in your build.gradle file.
plugins {
id 'org.springframework.boot' version '3.2.4'
id 'io.spring.dependency-management' version '1.1.4'
id 'org.graalvm.buildtools.native' version '0.9.28'
}
The build command is simple. For Maven, you run ./mvnw -Pnative native:compile. For Gradle, it’s ./gradlew nativeCompile. This process is more intensive than a regular build. It uses more CPU and memory and takes longer. But you only do it when creating a production artifact.
What about the dynamic parts of Java? Reflection, JDK proxies, and resource loading are common in frameworks like Spring. You might be using them without even knowing. The key is that Spring Boot’s AOT engine handles most of this for you. It generates JSON configuration files in target/classes/META-INF/native-image that tell the GraalVM compiler about these elements.
Sometimes, you’ll use a library that the AOT engine can’t automatically detect. You might see an error like ClassNotFoundException or MethodNotFoundException when you run your native executable. This means a class or method accessed via reflection is missing. You need to provide hints. You can create a special configuration class.
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
public class MyRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
// Register a class for reflection
hints.reflection().registerType(com.example.MyExternalClass.class,
MemberCategory.INVOKE_PUBLIC_METHODS);
// Register a resource file
hints.resources().registerPattern("my-config.json");
}
}
You then register this class with your main application using @ImportRuntimeHints(MyRuntimeHints.class). It’s a bit of extra work, but it’s straightforward. Have you ever wondered how much smaller a native image container can be?
The results can be staggering. A typical Spring Boot JAR running on a JRE needs a base container image that includes the entire operating system and JDK. A native executable can use a minimal base image like ubuntu:jammy or even gcr.io/distroless/base. Let’s look at a Dockerfile example.
FROM ghcr.io/graalvm/native-image-community:21-ol9 AS builder
WORKDIR /workspace
COPY . .
RUN ./mvnw -Pnative native:compile
FROM ubuntu:jammy
WORKDIR /app
COPY --from=builder /workspace/target/my-application .
EXPOSE 8080
ENTRYPOINT ["./my-application"]
The final image can be under 100MB, compared to often 300-400MB for a JVM-based image. The startup time moves from tens of seconds to tens of milliseconds. Memory usage often drops by half or more. This isn’t just a minor improvement; it changes how you think about scaling and deployment.
Is it all perfect? No. There are trade-offs. The build is slower. Some debugging tools you rely on, like attaching a live profiler, work differently. The “peak throughput” of a long-running native application might be slightly lower than a fully warmed-up JVM with a top-tier JIT compiler. But for most microservices, which are often idle or scaled to zero, the trade-off is overwhelmingly positive.
My journey into native images started with frustration over slow startup times. It led me to a tool that feels like it’s from the future. Creating an executable that starts before you can lift your finger from the enter key is a game-changer. It makes your applications feel instant, efficient, and truly cloud-native.
I encourage you to try it on a non-critical service. Run the build command and see what happens. The errors are now much clearer, and the Spring team has done immense work to smooth the path. What could you build if your services were instantly ready? If you found this guide helpful, please share it with a colleague or leave a comment with your own experience. Let’s build faster software, together.
As a best-selling author, I invite you to explore my books on Amazon. Don’t forget to follow me on Medium and show your support. Thank you! Your support means the world!
101 Books
101 Books is an AI-driven publishing company co-founded by author Aarav Joshi. By leveraging advanced AI technology, we keep our publishing costs incredibly low—some books are priced as low as $4—making quality knowledge accessible to everyone.
Check out our book Golang Clean Code available on Amazon.
Stay tuned for updates and exciting news. When shopping for books, search for Aarav Joshi to find more of our titles. Use the provided link to enjoy special discounts!
📘 Checkout my latest ebook for free on my channel!
Be sure to like, share, comment, and subscribe to the channel!
Our Creations
Be sure to check out our creations:
Investor Central | Investor Central Spanish | Investor Central German | Smart Living | Epochs & Echoes | Puzzling Mysteries | Hindutva | Elite Dev | JS Schools
We are on Medium
Tech Koala Insights | Epochs & Echoes World | Investor Central Medium | Puzzling Mysteries Medium | Science & Epochs Medium | Modern Hindutva