# GraalVM Native Image avec Spring Boot 3 en 2026 : compilation AOT pas à pas
> Guide complet pour compiler une application Spring Boot 3 en native image avec GraalVM. Configuration AOT, optimisations et déploiement en production.
- Published: 2026-03-21
- Updated: 2026-03-31
- Author: SharpSkill
- Tags: graalvm, spring boot 3, native image, aot compilation, performance java
- Reading time: 14 min
---
La compilation native avec GraalVM transforme une application Spring Boot 3 en exécutable natif. Le temps de démarrage passe de plusieurs secondes à quelques millisecondes, et la consommation mémoire diminue drastiquement. Ce guide détaille chaque étape de la configuration AOT jusqu'au déploiement en production.
> **Prérequis**
>
> GraalVM 22.3+ avec Native Image installé, Spring Boot 3.2+ et Maven ou Gradle. La compilation native nécessite plus de RAM (8 Go minimum recommandés) et prend plusieurs minutes.
## Comprendre la compilation AOT et Native Image
### Différence entre JIT et AOT
La JVM traditionnelle utilise la compilation Just-In-Time (JIT) : le bytecode est interprété puis compilé en code machine pendant l'exécution. GraalVM Native Image adopte l'approche Ahead-Of-Time (AOT) : tout le code est compilé avant l'exécution.
```text
┌─────────────────────────────────────────────────────────────┐
│ Compilation JIT │
├─────────────────────────────────────────────────────────────┤
│ │
│ .java → .class → JVM → Interprétation → JIT → Machine │
│ (runtime) (runtime) │
│ │
│ Avantages: Optimisations adaptatives, chargement rapide │
│ Inconvénients: Démarrage lent, consommation mémoire │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Compilation AOT │
├─────────────────────────────────────────────────────────────┤
│ │
│ .java → .class → GraalVM Native Image → Exécutable natif │
│ (build time) │
│ │
│ Avantages: Démarrage instantané, faible mémoire │
│ Inconvénients: Build long, pas de réflexion dynamique │
└─────────────────────────────────────────────────────────────┘
```
La compilation AOT analyse statiquement tout le code accessible depuis le point d'entrée. Tout code non détecté à la compilation est exclu de l'image native, ce qui explique les contraintes sur la réflexion et le chargement dynamique de classes.
### Architecture Spring AOT
Spring Boot 3 intègre nativement le support AOT. Le processus de compilation génère du code source additionnel qui remplace les mécanismes dynamiques par des équivalents statiques.
```java
// ApplicationConfig.java
// Configuration Spring standard
@Configuration
@EnableCaching
public class ApplicationConfig {
@Bean
public CacheManager cacheManager() {
// Bean créé dynamiquement au runtime en mode JIT
// Pré-généré statiquement en mode AOT
return new ConcurrentMapCacheManager("users", "products");
}
@Bean
@ConditionalOnProperty(name = "app.feature.enabled", havingValue = "true")
public FeatureService featureService() {
// Les conditions sont évaluées au build time en AOT
return new FeatureServiceImpl();
}
}
```
Le processus AOT de Spring génère automatiquement des fichiers dans `target/spring-aot/main` :
```text
target/spring-aot/main/
├── sources/ # Code Java généré
│ └── com/example/
│ └── ApplicationConfig__BeanDefinitions.java
├── resources/
│ └── META-INF/
│ └── native-image/
│ ├── reflect-config.json # Configuration réflexion
│ ├── resource-config.json # Ressources incluses
│ └── proxy-config.json # Proxies JDK
```
## Configuration du projet Spring Boot
### Dépendances Maven
La configuration Maven utilise le plugin Spring Boot avec le profil native. Les dépendances doivent être compatibles GraalVM.
```xml
4.0.0
org.springframework.boot
spring-boot-starter-parent
3.4.2
com.example
native-demo
1.0.0
21
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-data-jpa
org.postgresql
postgresql
runtime
org.springframework.boot
spring-boot-starter-validation
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-maven-plugin
org.graalvm.buildtools
native-maven-plugin
native
org.graalvm.buildtools
native-maven-plugin
-O2
--verbose
--enable-http
--enable-https
-Xmx8g
```
### Configuration Gradle équivalente
Pour les projets Gradle, la configuration native est similaire avec le plugin GraalVM natif.
```kotlin
// build.gradle.kts
// Configuration Gradle pour Spring Boot Native
plugins {
java
id("org.springframework.boot") version "3.4.2"
id("io.spring.dependency-management") version "1.1.7"
// Plugin GraalVM Native
id("org.graalvm.buildtools.native") version "0.10.4"
}
group = "com.example"
version = "1.0.0"
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
runtimeOnly("org.postgresql:postgresql")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
// Configuration du build native
graalvmNative {
binaries {
named("main") {
// Nom de l'exécutable généré
imageName = "native-demo"
// Options de compilation
buildArgs.addAll(
"-O2", // Niveau d'optimisation
"--enable-http", // Support HTTP
"--enable-https", // Support HTTPS
"--verbose" // Logs détaillés
)
// Configuration mémoire pour le build
jvmArgs.addAll("-Xmx8g")
}
named("test") {
// Tests natifs avec rapport
buildArgs.add("--verbose")
}
}
// Agent de tracing pour découverte automatique
agent {
defaultMode = "standard"
enabled = true
}
}
tasks.withType {
useJUnitPlatform()
}
```
> **GraalVM Tracing Agent**
>
> L'agent de tracing (`-agentlib:native-image-agent`) permet de découvrir automatiquement les appels de réflexion pendant l'exécution. Lancez l'application avec l'agent, exercez toutes les fonctionnalités, puis utilisez les fichiers de configuration générés.
## Gestion de la réflexion et des ressources
### Configuration manuelle de la réflexion
Certaines bibliothèques utilisent la réflexion de manière non détectable par l'analyse statique. La configuration manuelle devient nécessaire.
```json
// src/main/resources/META-INF/native-image/reflect-config.json
// Configuration des classes nécessitant la réflexion
[
{
"name": "com.example.entity.User",
"allDeclaredConstructors": true,
"allDeclaredMethods": true,
"allDeclaredFields": true
},
{
"name": "com.example.dto.UserDTO",
"allDeclaredConstructors": true,
"allDeclaredMethods": true,
"allDeclaredFields": true
},
{
"name": "com.example.config.DynamicProperties",
"methods": [
{ "name": "getValue", "parameterTypes": [] },
{ "name": "setValue", "parameterTypes": ["java.lang.String"] }
]
}
]
```
### Utilisation des RuntimeHints de Spring
Spring Boot 3 propose une API programmatique pour déclarer les hints natifs, plus maintenable que les fichiers JSON.
```java
// NativeHintsRegistrar.java
// Enregistrement programmatique des hints natifs
@Configuration
@ImportRuntimeHints(NativeHintsRegistrar.AppRuntimeHints.class)
public class NativeHintsRegistrar {
static class AppRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
// Enregistrement des classes pour réflexion
hints.reflection()
// Entités JPA avec tous les membres
.registerType(User.class, MemberCategory.values())
.registerType(Order.class, MemberCategory.values())
// DTOs avec constructeurs et getters/setters
.registerType(UserDTO.class,
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_DECLARED_METHODS,
MemberCategory.DECLARED_FIELDS
);
// Enregistrement des ressources à inclure
hints.resources()
// Fichiers de configuration
.registerPattern("application*.yml")
.registerPattern("application*.properties")
// Templates et fichiers statiques
.registerPattern("templates/*")
.registerPattern("static/**/*")
// Messages de validation
.registerPattern("ValidationMessages*.properties");
// Enregistrement des proxies JDK
hints.proxies()
.registerJdkProxy(
UserRepository.class,
Repository.class
);
// Sérialisation pour le caching
hints.serialization()
.registerType(User.class)
.registerType(ArrayList.class);
}
}
}
```
```java
// EntityRuntimeHints.java
// Hints automatiques pour les entités JPA
@Component
public class EntityRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
// Scan automatique des entités dans le package
ClassPathScanningCandidateComponentProvider scanner =
new ClassPathScanningCandidateComponentProvider(false);
scanner.addIncludeFilter(new AnnotationTypeFilter(Entity.class));
for (BeanDefinition bd : scanner.findCandidateComponents("com.example.entity")) {
try {
Class> entityClass = Class.forName(bd.getBeanClassName());
// Enregistre chaque entité pour la réflexion complète
hints.reflection().registerType(
entityClass,
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_DECLARED_METHODS,
MemberCategory.DECLARED_FIELDS
);
} catch (ClassNotFoundException e) {
// Log l'erreur sans interrompre le build
System.err.println("Entity class not found: " + bd.getBeanClassName());
}
}
}
}
```
## Compilation et optimisation de l'image native
### Commandes de build
La compilation native s'effectue avec Maven ou Gradle. Le processus prend plusieurs minutes et consomme beaucoup de ressources.
```bash
# Build Maven avec profil native
# Génère l'exécutable dans target/
mvn -Pnative native:compile
# Build Gradle
# Génère l'exécutable dans build/native/nativeCompile/
./gradlew nativeCompile
# Build avec tests natifs inclus
mvn -Pnative native:compile -DskipTests=false
# Build avec agent de tracing activé
mvn -Pnative -Dagent=true test
mvn -Pnative native:compile
```
### Options d'optimisation avancées
Les options de compilation influencent la taille de l'image, le temps de démarrage et les performances runtime.
```xml
org.graalvm.buildtools
native-maven-plugin
-O3
--pgo-instrument
-H:+CompressStrings
--gc=serial
--initialize-at-build-time=org.slf4j
-H:-IncludeAllTimeZones
-H:+ReportExceptionStackTraces
--verbose
--enable-monitoring=heapdump,jfr
false
false
```
```java
// BuildTimeInitializer.java
// Initialisation au build time pour réduire le démarrage
@Configuration
public class BuildTimeInitializer {
// Ces configurations sont évaluées au build time
// et non au runtime
static {
// Initialise les loggers au build time
LoggerFactory.getLogger(BuildTimeInitializer.class);
}
@Bean
@NativeHint(options = "--initialize-at-build-time=com.example.Constants")
public ConstantsProvider constantsProvider() {
// Les constantes sont calculées une seule fois au build
return new ConstantsProvider();
}
}
```
### Comparaison des performances
Les gains de performance avec la compilation native sont significatifs.
```text
┌─────────────────────────────────────────────────────────────────────┐
│ Comparaison JIT vs Native │
├─────────────────────┬─────────────────┬─────────────────────────────┤
│ Métrique │ JIT (JVM) │ Native (GraalVM) │
├─────────────────────┼─────────────────┼─────────────────────────────┤
│ Temps de démarrage │ 2.5 - 5 sec │ 50 - 200 ms │
│ Mémoire RSS │ 200 - 400 MB │ 50 - 100 MB │
│ Taille exécutable │ JAR ~30 MB │ Binary ~80 MB │
│ Temps premier req. │ 100 - 500 ms │ < 10 ms │
│ Peak throughput │ Excellent │ Bon (85-95% du JIT) │
│ Temps de build │ 30 sec │ 3 - 10 min │
└─────────────────────┴─────────────────┴─────────────────────────────┘
```
> **Peak Performance**
>
> Le throughput maximum en mode natif peut être légèrement inférieur au mode JIT car les optimisations adaptatives du JIT ne sont pas disponibles. Pour les workloads à haute performance soutenue, évaluer les deux modes.
## Résolution des problèmes courants
### Erreurs de réflexion
L'erreur la plus fréquente concerne la réflexion non déclarée. L'exception indique la classe manquante.
```java
// ReflectionErrorHandler.java
// Diagnostic et résolution des erreurs de réflexion
@Component
@Slf4j
public class ReflectionErrorHandler {
// Erreur typique :
// java.lang.ClassNotFoundException: com.example.SomeClass
// au moment de l'accès réflexif
// Solution 1 : Ajouter la configuration manuelle
// src/main/resources/META-INF/native-image/reflect-config.json
// Solution 2 : Utiliser l'annotation @RegisterReflection
@RegisterReflection(classes = {
SomeClass.class,
AnotherClass.class
})
public void configureReflection() {
// Les classes annotées seront disponibles pour réflexion
}
// Solution 3 : RuntimeHints programmatique
public void registerHints(RuntimeHints hints) {
hints.reflection().registerType(
SomeClass.class,
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_DECLARED_METHODS
);
}
}
```
### Ressources manquantes
Les fichiers de ressources doivent être explicitement déclarés pour être inclus dans l'image native.
```json
// src/main/resources/META-INF/native-image/resource-config.json
// Configuration des ressources à inclure
{
"resources": {
"includes": [
{"pattern": "application\\.yml"},
{"pattern": "application-.*\\.yml"},
{"pattern": "messages.*\\.properties"},
{"pattern": "templates/.*\\.html"},
{"pattern": "static/.*"},
{"pattern": "db/migration/.*\\.sql"}
],
"excludes": [
{"pattern": ".*\\.java"},
{"pattern": ".*\\.class"}
]
},
"bundles": [
{"name": "messages"},
{"name": "ValidationMessages"}
]
}
```
```java
// ResourceHintsConfig.java
// Configuration programmatique des ressources
@Configuration
@ImportRuntimeHints(ResourceHintsConfig.ResourceHints.class)
public class ResourceHintsConfig {
static class ResourceHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
// Fichiers YAML/Properties
hints.resources()
.registerPattern("application*.yml")
.registerPattern("application*.properties");
// Templates Thymeleaf
hints.resources().registerPattern("templates/**");
// Scripts SQL Flyway
hints.resources().registerPattern("db/migration/*.sql");
// Fichiers statiques
hints.resources().registerPattern("static/**");
// Bundles de messages
hints.resources().registerResourceBundle("messages");
hints.resources().registerResourceBundle("ValidationMessages");
}
}
}
```
### Problèmes avec les proxies
Les proxies JDK et CGLIB nécessitent une configuration spécifique pour fonctionner en mode natif.
```java
// ProxyConfiguration.java
// Gestion des proxies pour compilation native
@Configuration
public class ProxyConfiguration implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
// Proxies JDK pour interfaces Spring Data
hints.proxies().registerJdkProxy(
UserRepository.class,
Repository.class,
CrudRepository.class
);
// Proxies pour interfaces de service
hints.proxies().registerJdkProxy(
PaymentService.class,
TransactionalService.class
);
}
// Alternative : forcer les proxies CGLIB
@Bean
public BeanFactoryPostProcessor forceProxyTargetClass() {
return beanFactory -> {
// Utilise CGLIB au lieu de JDK proxies
// Plus compatible avec la compilation native
};
}
}
```
## Déploiement Docker et Kubernetes
### Dockerfile multi-stage optimisé
Le build multi-stage sépare la compilation de l'exécution pour une image minimale.
```dockerfile
# Dockerfile
# Build multi-stage pour Spring Boot Native
# Stage 1: Build avec GraalVM
FROM ghcr.io/graalvm/graalvm-community:21 AS builder
# Installe Native Image
RUN gu install native-image
WORKDIR /app
# Copie les fichiers de build
COPY pom.xml .
COPY src ./src
# Installe Maven
RUN microdnf install -y maven
# Build native avec cache des dépendances
RUN --mount=type=cache,target=/root/.m2 \
mvn -Pnative native:compile -DskipTests
# Stage 2: Image runtime minimale
FROM gcr.io/distroless/base-debian12
WORKDIR /app
# Copie l'exécutable natif
COPY --from=builder /app/target/native-demo /app/native-demo
# Port exposé
EXPOSE 8080
# Healthcheck
HEALTHCHECK --interval=10s --timeout=3s --start-period=5s \
CMD ["/app/native-demo", "--health"]
# Exécution
ENTRYPOINT ["/app/native-demo"]
```
```dockerfile
# Dockerfile.alpine
# Alternative avec Alpine pour image encore plus légère
FROM ghcr.io/graalvm/native-image-community:21-muslib AS builder
WORKDIR /app
COPY pom.xml .
COPY src ./src
RUN --mount=type=cache,target=/root/.m2 \
mvn -Pnative native:compile \
-Dspring-boot.aot.jvmArguments="-Dspring.aot.processing.resource.matching.strategy=GLOB" \
-DskipTests
# Image Alpine minimale (< 20 MB)
FROM alpine:3.19
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY --from=builder /app/target/native-demo /app/native-demo
EXPOSE 8080
ENTRYPOINT ["/app/native-demo"]
```
### Déploiement Kubernetes avec ressources optimisées
Les applications natives nécessitent moins de ressources que les applications JVM traditionnelles.
```yaml
# kubernetes/deployment.yaml
# Déploiement Kubernetes optimisé pour native
apiVersion: apps/v1
kind: Deployment
metadata:
name: native-demo
spec:
replicas: 3
selector:
matchLabels:
app: native-demo
template:
metadata:
labels:
app: native-demo
spec:
containers:
- name: native-demo
image: registry.example.com/native-demo:1.0.0
ports:
- containerPort: 8080
# Ressources réduites grâce au native
resources:
requests:
memory: "64Mi" # vs 256Mi pour JVM
cpu: "50m" # vs 200m pour JVM
limits:
memory: "128Mi" # vs 512Mi pour JVM
cpu: "200m" # vs 500m pour JVM
# Probes rapides (démarrage instantané)
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 1 # vs 30s pour JVM
periodSeconds: 5
failureThreshold: 3
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 2 # vs 60s pour JVM
periodSeconds: 10
failureThreshold: 3
# Variables d'environnement
env:
- name: SPRING_PROFILES_ACTIVE
value: "production"
- name: JAVA_TOOL_OPTIONS
value: "" # Pas besoin d'options JVM
---
apiVersion: v1
kind: Service
metadata:
name: native-demo
spec:
selector:
app: native-demo
ports:
- port: 80
targetPort: 8080
type: ClusterIP
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: native-demo-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: native-demo
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
```
> **Scaling rapide**
>
> Le temps de démarrage instantané permet un scaling horizontal très rapide. Les nouveaux pods sont prêts en quelques secondes, idéal pour les workloads avec pics de charge.
## Tests et validation de l'image native
### Configuration des tests natifs
Les tests peuvent également être compilés et exécutés en mode natif pour valider le comportement.
```java
// NativeIntegrationTest.java
// Tests d'intégration pour validation native
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestPropertySource(properties = {
"spring.datasource.url=jdbc:h2:mem:testdb",
"spring.jpa.hibernate.ddl-auto=create-drop"
})
class NativeIntegrationTest {
@Autowired
private TestRestTemplate restTemplate;
@Autowired
private UserRepository userRepository;
@Test
void shouldCreateAndRetrieveUser() {
// Arrange : création d'un utilisateur
UserDTO request = new UserDTO("John", "john@example.com");
// Act : appel API
ResponseEntity createResponse = restTemplate.postForEntity(
"/api/users",
request,
UserDTO.class
);
// Assert : vérification création
assertThat(createResponse.getStatusCode()).isEqualTo(HttpStatus.CREATED);
assertThat(createResponse.getBody()).isNotNull();
assertThat(createResponse.getBody().getName()).isEqualTo("John");
// Vérification récupération
Long userId = createResponse.getBody().getId();
ResponseEntity getResponse = restTemplate.getForEntity(
"/api/users/{id}",
UserDTO.class,
userId
);
assertThat(getResponse.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(getResponse.getBody().getEmail()).isEqualTo("john@example.com");
}
@Test
void shouldHandleReflectionCorrectly() {
// Test spécifique pour valider la configuration de réflexion
User user = new User();
user.setName("Test");
user.setEmail("test@example.com");
// L'ORM utilise la réflexion pour mapper les entités
User saved = userRepository.save(user);
assertThat(saved.getId()).isNotNull();
assertThat(userRepository.findById(saved.getId())).isPresent();
}
}
```
```xml
org.graalvm.buildtools
native-maven-plugin
--verbose
test-native
test
test
```
## Conclusion
La compilation native avec GraalVM transforme les applications Spring Boot 3 en exécutables performants. Les points essentiels à retenir :
**Configuration du projet :**
- ✅ Spring Boot 3.2+ avec plugin GraalVM natif
- ✅ RuntimeHints pour la réflexion et les ressources
- ✅ Agent de tracing pour découverte automatique
**Optimisations de build :**
- ✅ Options de compilation adaptées (O2/O3, GC, compression)
- ✅ Initialisation au build time pour les composants statiques
- ✅ Quickbuild pour le développement, full build pour la production
**Résolution des problèmes :**
- ✅ Configuration explicite de la réflexion pour les bibliothèques tierces
- ✅ Déclaration des ressources à inclure
- ✅ Gestion des proxies JDK et CGLIB
**Déploiement :**
- ✅ Images Docker multi-stage avec distroless
- ✅ Ressources Kubernetes réduites (64 Mi vs 256 Mi)
- ✅ Probes avec délais minimaux (démarrage instantané)
La compilation native est idéale pour les microservices, les fonctions serverless et les environnements à ressources limitées. Le temps de démarrage instantané et la faible consommation mémoire compensent largement le temps de build plus long.
---
Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack.
HTML version of this page: https://sharpskill.dev/fr/blog/spring-boot/graalvm-native-image-spring-boot-3-aot-compilation