Enterprise Java

GraalVM and Spring Boot – Best Practices for Native Image Spring Apps

Spring Boot has long been the go-to framework for building production-grade Java applications quickly. But one of the biggest criticisms of Java has always been slow startup times and high memory usage. That’s where GraalVM Native Image comes in—allowing developers to compile Java applications into native executables that start up in milliseconds and consume far less memory.

If you’re building cloud-native apps, microservices, or serverless functions, Spring Boot + GraalVM can be a game-changer. But as with any powerful tool, you need to know the best practices to get the most out of it.

Let’s dive in.

Why Use GraalVM Native Image with Spring Boot?

Before talking best practices, it’s worth clarifying the benefits:

  • Instant Startup – From seconds down to tens of milliseconds.
  • Lower Memory Footprint – Great for cloud deployments and containers.
  • Smaller Attack Surface – Only required code is compiled into the image.
  • Optimized for Scale – Perfect for microservices that spin up and down frequently.

For example, a simple Spring Boot REST API that takes ~2.5 seconds to start on the JVM may start in just 50 ms as a native image.

Best Practices for Building Native Spring Boot Apps

1. Use the Right Spring Boot Version

  • Start with Spring Boot 3+ since it has first-class support for GraalVM native images via Spring AOT (Ahead-of-Time) processing.
  • Add the Spring Native plugin:
<plugin>
  <groupId>org.graalvm.buildtools</groupId>
  <artifactId>native-maven-plugin</artifactId>
  <version>0.10.1</version>
</plugin>

Or with Gradle:

plugins {
    id 'org.graalvm.buildtools.native' version '0.10.1'
}

2. Leverage Spring AOT Processing

Spring AOT transforms your application code and configuration at build time to remove unnecessary reflection and dynamic class loading.

  • Enable AOT mode by building with:
./mvnw -Pnative native:compile
  • This produces a self-contained native binary ready for deployment.

3. Be Mindful of Reflection & Proxies

Native images don’t handle reflection gracefully by default. If your Spring Boot app uses libraries that rely heavily on reflection (like Hibernate), you’ll need to:

  • Add reflection hints:
@ReflectionHint(types = MyEntity.class)
public class MyHints implements NativeConfiguration {}
  • Or register configuration in META-INF/native-image/ JSON files.

👉 Best practice: use Spring’s AOT-generated hints whenever possible to minimize manual work.

4. Optimize Dependencies

Not all dependencies are native-image friendly. For example:

  • Avoid libraries that dynamically generate classes at runtime.
  • Prefer Jakarta Persistence (via Hibernate 6.x) which has native support.
  • Use Spring Boot’s dependency compatibility list as a reference.

5. Tune Memory and Performance

Native images are smaller but not always faster at raw throughput than the JVM.

  • Use native images for startup-sensitive apps (microservices, CLI tools, serverless).
  • Stick with the JVM for long-running, throughput-heavy apps (batch jobs, big data pipelines).

Example:

  • Spring Boot API as native image: cold-start in 60 ms.
  • Same API on JVM: cold-start in 2.4 seconds, but higher throughput after warm-up.

6. Containerize Your Native Images

If deploying to Kubernetes or Docker:

  • Use a distroless base image for minimal footprint:
FROM gcr.io/distroless/base
COPY build/myapp /
CMD ["/myapp"]
  • Keep your images small (<50MB) compared to JVM-based images (~300MB).

7. Embrace Observability

Native images change runtime behavior, so ensure monitoring still works:

  • Add Micrometer with Prometheus or Grafana.
  • Use Spring Boot Actuator endpoints (fully supported in native mode).

Example: REST API with Native Image

A simple Spring Boot controller:

@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello() {
        return "Hello from GraalVM Native!";
    }
}

Build and run as a native image:

./mvnw -Pnative native:compile
./target/hello

Result:

Startup time: 0.045s
Memory: ~30MB RSS

JVM vs Native – Quick Comparison

AspectJVM AppNative Image
Startup Time2–3s~50ms
Memory Usage150–300MB30–60MB
Binary Size40–80MB
Throughput (long-running)Higher (JIT optimizations)Slightly lower
Best Use CaseBatch jobs, data processingMicroservices, serverless, CLIs

Tip: Don’t Go “Native” Everywhere

It’s tempting to rebuild everything as a native image, but native images are not a silver bullet.

  • ✅ Perfect for cloud microservices, serverless functions, and edge deployments.
  • ❌ Not always ideal for long-running applications where JVM’s JIT gives better throughput.

Think of native images as turbo-charged engines: amazing for quick acceleration, but not always best for long highway drives.

Final Thoughts

GraalVM native images bring Java into the serverless and cloud-native era. By combining Spring Boot 3, AOT processing, and careful dependency management, you can create apps that are faster, lighter, and cheaper to run in the cloud.

The best practice? Mix and match. Use JVM apps where throughput is king, and native images where startup time and memory efficiency matter most.

Your future Spring Boot deployments might not just run faster—they might also run smarter.

Eleftheria Drosopoulou

Eleftheria is an Experienced Business Analyst with a robust background in the computer software industry. Proficient in Computer Software Training, Digital Marketing, HTML Scripting, and Microsoft Office, they bring a wealth of technical skills to the table. Additionally, she has a love for writing articles on various tech subjects, showcasing a talent for translating complex concepts into accessible content.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

1 Comment
Oldest
Newest Most Voted
Thomas Wuerthinger
9 months ago

For long-running applications, one can use profile-guided optimizations (PGO) to achieve also the best throughput with GraalVM native images. With this setting, also long-running applications benefit from better execution characteristics with native image compared to the JVM. Find more information about this here: https://www.graalvm.org/latest/reference-manual/native-image/guides/optimize-native-executable-with-pgo/

Back to top button