A Spring Boot service that starts in 50 milliseconds and runs in 80 MB of memory sounds like a different platform from the Java most teams know. GraalVM native image makes that possible by compiling the application ahead of time into a standalone executable. It also changes how you build, test and debug, and in 2025 Oracle changed GraalVM's role in the Java ecosystem. This guide explains how native images work with Spring Boot, where they pay off, what they cost, and when the JDK's own ahead-of-time cache from Project Leyden is the simpler answer.

How native image works

A regular Java application starts a JVM, loads classes, interprets bytecode and gradually compiles hot code with the JIT. A native image does that work at build time: GraalVM analyzes the application starting from main, includes only reachable code, initializes parts of it, and produces a platform-specific executable (GraalVM: Native Image).

The analysis relies on a closed-world assumption: everything the application can ever run must be known at build time. Reflection, dynamic proxies, resources and serialization must either be detectable by static analysis or declared through metadata ("hints").

How Spring Boot supports it

Spring Boot has first-class native support through Spring AOT (Spring Boot: GraalVM native images). At build time, Spring AOT evaluates the application context, generates source code for bean definitions, and produces the reflection and resource hints GraalVM needs. Most Spring projects and many popular libraries ship hints already.

Building is a plugin task:

# Gradle, with the GraalVM Native Build Tools plugin applied
./gradlew nativeCompile
./build/native/nativeCompile/orders-service

# Maven
./mvnw -Pnative native:compile

# Or a container image with Cloud Native Buildpacks, no local GraalVM needed
./gradlew bootBuildImage    # with the native image buildpack enabled

What you gain

  • Startup in tens of milliseconds instead of seconds — the difference between scale-to-zero being practical or not.
  • Lower memory footprint, often a fraction of the equivalent JVM process, which means smaller containers and denser nodes.
  • No warm-up: peak performance is available immediately, which matters for short-lived jobs and functions.
  • Smaller attack surface: only reachable code is in the binary.

What you pay

  • Build time and resources. Native compilation of a typical Spring Boot service takes minutes and several gigabytes of RAM, versus seconds for a JAR. CI needs to account for it; see CI/CD without pain.
  • Fixed configuration at build time. Spring's @Profile and @ConditionalOnProperty are evaluated during the AOT phase, so the set of beans can't change at runtime the way it can on the JVM.
  • Dynamic features need hints. Libraries that use reflection, dynamic class loading or bytecode generation at runtime may need extra configuration or don't work at all.
  • Peak throughput may be lower than a fully warmed JIT for long-running, CPU-heavy services, unless you use profile-guided optimization.
  • Different debugging and profiling. Familiar JVM tools such as JFR are only partially available; native debugging requires different tooling.
  • Platform-specific binaries. You build per OS and CPU architecture.

Custom hints for your own reflective code look like this:

@Configuration
@ImportRuntimeHints(ReportHints.class)
class ReportConfig { }

class ReportHints implements RuntimeHintsRegistrar {
    @Override
    public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
        hints.reflection().registerType(ReportRow.class,
            MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS);
        hints.resources().registerPattern("reports/*.jrxml");
    }
}

Test the native binary, not just the JVM build: Spring Boot can run your test suite in native mode (./gradlew nativeTest), which catches missing hints before production.

Oracle's 2025 GraalVM changes

In September 2025, Oracle announced that the GraalVM team would focus on non-Java languages such as GraalPy and GraalJS, that GraalVM for JDK 24 was the last release licensed and supported as part of Oracle Java SE products, and that the goals of faster startup, faster warm-up and smaller footprint for Java would be pursued in OpenJDK's Project Leyden (InfoWorld, 2025).

What this means in practice:

  • GraalVM 25, including Native Image, was released and the Community Edition remains available; Spring Boot continues to support native images.
  • Commercial support for native image as part of an Oracle Java SE subscription ended; other distributions and vendors provide their own builds and support.
  • The long-term, standard-Java direction for startup and footprint is Project Leyden.

For new projects, that shifts the default question from "should we go native?" to "is Leyden's AOT cache enough?".

The alternative: JDK 25's AOT cache

Since JDK 24, the JVM can create an ahead-of-time cache from a training run that stores loaded and linked classes, and since JDK 25 also method profiles, so the next start skips that work and the JIT optimizes immediately (JEP 514; JEP 515). You keep the full JVM — all dynamic features, JFR, the usual tools — and gain much of the startup improvement with a one-line change. We cover it in Java 25 LTS: what's new.

Criterion JVM JVM + AOT cache (JDK 25) Native image
Startup Seconds Noticeably faster Tens of milliseconds
Memory footprint Highest Similar to JVM Lowest
Peak throughput Best after warm-up Best, reached sooner Good; PGO helps
Dynamic features All All Need hints, some unsupported
Build complexity Simple Training run in CI Long builds, per-platform
Tooling Full Full Partial

When native image is the right call

  • Scale-to-zero and serverless: functions and services that start on demand.
  • CLI tools distributed to users or developers.
  • Very dense deployments where memory per instance drives cost, such as many small services per node.
  • Short-lived batch jobs that never benefit from JIT warm-up.

When to stay on the JVM

  • Long-running services with steady traffic, where startup happens rarely and peak throughput matters.
  • Applications built on heavily dynamic libraries or frameworks.
  • Teams without capacity to maintain hints, native tests and longer builds.

For these, the JVM with virtual threads and an AOT cache is usually the better trade-off; see Virtual threads in Spring Boot.

A decision checklist

Answer these before committing a service to native image:

  • Does the service start often enough — autoscaling, scale-to-zero, short jobs — for startup time to matter?
  • Is memory per instance a significant part of your infrastructure bill?
  • Do all key libraries support native image or ship reachability metadata?
  • Can CI afford native builds of several minutes and several gigabytes of RAM per build?
  • Is the team ready to run the test suite in native mode and debug native-specific issues?

If most answers are "no", start with the JVM plus an AOT cache and revisit later.

CI tips for native builds

  • Build natively only where it matters. Run regular JVM tests on every pull request and native builds plus nativeTest on the main branch or nightly.
  • Cache dependencies and the Gradle or Maven build aggressively; native compilation itself is not incremental.
  • Use larger runners for native jobs, or self-hosted runners on your own cluster, as discussed in CI/CD without pain.
  • Build per architecture. Produce amd64 and arm64 binaries in separate jobs and publish a multi-architecture image.
  • Track binary size and startup time as metrics in CI so regressions are visible.

FAQ

Does native image work with Spring Data JPA and Hibernate? Yes, Spring Boot and Hibernate provide the necessary hints for common setups. Test your specific queries and mappings in native mode.

Can we use Kotlin? Yes. Spring Boot's native support covers Kotlin applications.

Is GraalVM native image deprecated? No. GraalVM 25 ships Native Image and Spring Boot supports it, but Oracle no longer bundles it with Java SE subscriptions and points to Project Leyden for the standard Java path.

How much memory does a native Spring Boot service use? It depends on the application; small services often run in tens of megabytes. Measure your own with production-like load.

Can we mix native and JVM services in one system? Yes, and it is common. Use native images for the services where startup and memory matter most, such as on-demand workers and functions, and keep the rest on the JVM with an AOT cache. Both expose the same HTTP and messaging interfaces.

Does native image improve security? It reduces the attack surface because unreachable code and the JIT are not included, but it doesn't replace dependency scanning and patching.

Sources

  1. GraalVM. Native Image reference manual.
  2. Spring Boot. Introducing GraalVM native images.
  3. InfoWorld (2025). GraalVM 25 arrives, backed by JDK 25.
  4. OpenJDK. JEP 483: Ahead-of-Time Class Loading & Linking, JEP 514, JEP 515.