Back to blog
GraalVMNativeSpring Boot
GraalVM Native Image: Build Fast, Start Faster
Conference · 1 February 2025
GraalVM Native Image compiles your Java application ahead-of-time (AOT) into a native executable. The result: sub-second startup and lower memory footprint, ideal for serverless and containers.
When to use Native Image
- Serverless / FaaS – cold start matters; native binaries start in milliseconds.
- CLI tools – instant startup improves UX.
- Containers – smaller memory footprint and faster startup.
Native Image is not always the right choice. Long-running servers with heavy JIT optimization may perform better on the JVM. Use it where startup and footprint are the main concerns.
Basic build with Maven
Add the Native Image plugin and build:
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>0.10.1</version>
<executions>
<execution>
<id>build-native</id>
<goals><goal>compile-no-fallback</goal></goals>
<phase>package</phase>
</execution>
</executions>
</plugin>
Then:
./mvnw -Pnative native:compile
Your executable will be in target/ with the same name as your artifact.
Spring Boot 3 and Native
Spring Boot 3 supports GraalVM Native out of the box. Create the project with the native dependency:
curl https://start.spring.io/starter.zip -d type=maven-project \
-d language=java -d javaVersion=21 \
-d bootVersion=3.2.0 \
-d dependencies=web,native \
-o app.zip
Key points:
- Reflection and resources – Spring Native handles most registration; for custom code you may need
@RegisterReflectionForBindingorRuntimeHints. - Build time – native compilation can take minutes. Use it in CI, not on every local build.
- Debugging – run with
./mvnw spring-boot:runduring development; switch to native for packaging.
A simple Kotlin example
// Application.kt
@SpringBootApplication
@RestController
class Application {
@GetMapping("/")
fun home(): Map<String, String> = mapOf(
"message" to "Hello from Native Image",
"java" to System.getProperty("java.version")
)
}
fun main(args: Array<String>) {
runApplication<Application>(*args)
}
Build and run:
./gradlew nativeCompile
./build/native/nativeCompile/graalvm-native-image-guide
Summary
| Concern | JVM (JIT) | Native Image |
|---|---|---|
| Startup | Seconds | Milliseconds |
| Memory | Higher | Lower |
| Throughput | Often higher | Often good |
| Build time | Fast | Slower |
Use Native Image where startup and footprint matter; keep the classic JVM where peak throughput is king.