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:

  1. Reflection and resources – Spring Native handles most registration; for custom code you may need @RegisterReflectionForBinding or RuntimeHints.
  2. Build time – native compilation can take minutes. Use it in CI, not on every local build.
  3. Debugging – run with ./mvnw spring-boot:run during 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

ConcernJVM (JIT)Native Image
StartupSecondsMilliseconds
MemoryHigherLower
ThroughputOften higherOften good
Build timeFastSlower

Use Native Image where startup and footprint matter; keep the classic JVM where peak throughput is king.