# 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