Backend Development 12 min read

Spring Boot 3.0 AOT Compilation with GraalVM: A Step‑by‑Step Guide

This article explains how to prepare the environment, configure GraalVM, use Maven plugins, and apply Spring Boot 3.0 AOT and RuntimeHints to compile a Spring Boot application into a fast‑starting native executable, including Docker packaging and underlying AOT principles.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Spring Boot 3.0 AOT Compilation with GraalVM: A Step‑by‑Step Guide

Prerequisites

Before starting, review the new features of Spring 6 ( https://github.com/spring-projects/spring-framework/wiki/What%27s-New-in-Spring-Framework-6.x ) and Spring Boot 3.0 documentation ( https://docs.spring.io/spring-boot/docs/current/reference/html/ ). Install GraalVM matching your JDK version (JDK 17+ is required for Spring Boot 3.0) from the official releases ( https://github.com/graalvm/graalvm-ce-builds/releases ) and verify the installation with java -version . Maven should also be installed (preferably with Alibaba Cloud acceleration).

The motivation is to address the Serverless environment by reducing Spring Boot startup time: compiling Java bytecode directly to native machine code eliminates the JVM warm‑up overhead, while RuntimeHints preserve reflection, dynamic proxies, and other features.

Packaging Spring Boot 3.0

1. Project preparation

Add the following dependencies and plugins to your pom.xml :

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.2.5</version>
    <relativePath/>
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

<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>

Sample application code:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class Controller {
    @GetMapping("/demo1")
    public String demo1() {
        return "hello world";
    }
}

2. Build the native image

Run the Maven command with the native profile (requires GraalVM and a C toolchain, preferably on Linux):

# Note: use GraalVM environment and C toolchain
mvn -Pnative native:compile

The process takes longer than a regular JAR build but produces both a native executable and a JAR. The native binary starts in a few tens of milliseconds, while the JAR starts noticeably slower.

3. Docker packaging

# Build Docker image with native binary
mvn -Pnative spring-boot:build-image

docker run --rm -p 8080:8080 demo

# Pass parameters via environment variable
docker run --rm -p 8080:8080 -e methodName=test demo

# Retrieve the parameter in code
String methodName = System.getenv("methodName");
// or using Spring Environment
environment.getProperty("methodName");

Docker build downloads a Java base image, which may be slow, but the resulting container starts in milliseconds.

Understanding AOT and RuntimeHints

AOT (Ahead‑of‑Time) compilation moves bean scanning from runtime to compile time. Spring provides several mechanisms to inform GraalVM about reflective access, dynamic proxies, and other features that would otherwise be lost:

RuntimeHints – programmatic API to register reflection, proxy, and resource hints.

RuntimeHintsRegistrar – implement this interface and register it with @ImportRuntimeHints .

@RegisterReflectionForBinding – annotation to declare classes whose reflective members should be compiled.

@Reflective – annotate constructors or methods of beans that need reflection.

Example of a RuntimeHintsRegistrar implementation:

@Component
@ImportRuntimeHints(UserService.MyServiceRuntimeHints.class)
public class UserService {
    public String test() {
        // reflective call example
        Method m = MyService.class.getMethod("test");
        return (String) m.invoke(MyService.class.newInstance());
    }

    static class MyServiceRuntimeHints implements RuntimeHintsRegistrar {
        @Override
        public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
            try {
                hints.reflection().registerConstructor(MyService.class.getConstructor(), ExecutableMode.INVOKE);
                hints.reflection().registerMethod(MyService.class.getMethod("test"), ExecutableMode.INVOKE);
            } catch (NoSuchMethodException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

When method names are supplied via environment variables, the same RuntimeHints approach must be used to register the dynamically chosen method.

For JDK dynamic proxies, register the proxy interface:

hints.proxies().registerJdkProxy(UserInterface.class);

AOT compilation process

The Maven goal mvn -Pnative native:compile invokes the native-maven-plugin , which runs ProcessAotMojo.executeAot() . This triggers the SpringApplicationAotProcessor and ContextAotProcessor to generate Java source files under spring-aot/main/sources , compile them into target/classes , and copy GraalVM configuration files ( reflect-config.json , proxy-config.json ) into the same output directory. The generated classes contain pre‑computed BeanDefinition objects, allowing the native image to skip bean scanning at startup.

Finally, the native image can be executed directly or inside a Docker container, providing dramatically faster startup times compared with a traditional JVM‑based Spring Boot application.

JavaMavenSpring BootGraalVMnative-imageAOTRuntimeHints
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.