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.

GraalVM Native Image avec Spring Boot 3 : compilation AOT et optimisation des performances

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.

ApplicationConfig.javajava
// 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
<!-- pom.xml -->
<!-- Configuration complète pour Spring Boot 3 Native -->
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         https://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

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

    <groupId>com.example</groupId>
    <artifactId>native-demo</artifactId>
    <version>1.0.0</version>

    <properties>
        <java.version>21</java.version>
    </properties>

    <dependencies>
        <!-- Starter Web avec support native intégré -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- JPA avec Hibernate 6 compatible native -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <!-- Driver PostgreSQL compatible native -->
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>

        <!-- Validation avec hints natifs -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>

        <!-- Tests avec support native -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>

            <!-- Plugin GraalVM pour compilation native -->
            <plugin>
                <groupId>org.graalvm.buildtools</groupId>
                <artifactId>native-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <!-- Profil pour build native -->
    <profiles>
        <profile>
            <id>native</id>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.graalvm.buildtools</groupId>
                        <artifactId>native-maven-plugin</artifactId>
                        <configuration>
                            <!-- Options de build native -->
                            <buildArgs>
                                <!-- Optimisations pour la taille -->
                                <buildArg>-O2</buildArg>
                                <!-- Génère un rapport de build -->
                                <buildArg>--verbose</buildArg>
                                <!-- Active le support HTTP/2 -->
                                <buildArg>--enable-http</buildArg>
                                <buildArg>--enable-https</buildArg>
                            </buildArgs>
                            <!-- Mémoire pour le build -->
                            <jvmArgs>
                                <jvmArg>-Xmx8g</jvmArg>
                            </jvmArgs>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>

</project>

Configuration Gradle équivalente

Pour les projets Gradle, la configuration native est similaire avec le plugin GraalVM natif.

build.gradle.ktskotlin
// 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<Test> {
    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.

src/main/resources/META-INF/native-image/reflect-config.jsonjson
// 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.

NativeHintsRegistrar.javajava
// 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);
        }
    }
}
EntityRuntimeHints.javajava
// 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());
            }
        }
    }
}

Prêt à réussir tes entretiens Spring Boot ?

Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.

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
<!-- pom.xml -->
<!-- Configuration avancée du build native -->
<plugin>
    <groupId>org.graalvm.buildtools</groupId>
    <artifactId>native-maven-plugin</artifactId>
    <configuration>
        <buildArgs>
            <!-- Niveau d'optimisation (0-3, défaut: 2) -->
            <buildArg>-O3</buildArg>

            <!-- Optimisation pour temps de démarrage -->
            <buildArg>--pgo-instrument</buildArg>

            <!-- Compression de l'exécutable (réduit taille) -->
            <buildArg>-H:+CompressStrings</buildArg>

            <!-- Garbage collector optimal pour containers -->
            <buildArg>--gc=serial</buildArg>

            <!-- Initialisation au build time -->
            <buildArg>--initialize-at-build-time=org.slf4j</buildArg>

            <!-- Debug symbols (désactiver en prod) -->
            <buildArg>-H:-IncludeAllTimeZones</buildArg>

            <!-- Rapport de build détaillé -->
            <buildArg>-H:+ReportExceptionStackTraces</buildArg>
            <buildArg>--verbose</buildArg>

            <!-- Support monitoring -->
            <buildArg>--enable-monitoring=heapdump,jfr</buildArg>
        </buildArgs>

        <!-- Quickbuild pour développement (plus rapide, moins optimisé) -->
        <quickBuild>false</quickBuild>

        <!-- Fallback au jar si native échoue -->
        <fallback>false</fallback>
    </configuration>
</plugin>
BuildTimeInitializer.javajava
// 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.

ReflectionErrorHandler.javajava
// 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.

src/main/resources/META-INF/native-image/resource-config.jsonjson
// 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"}
  ]
}
ResourceHintsConfig.javajava
// 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.

ProxyConfiguration.javajava
// 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.

NativeIntegrationTest.javajava
// 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<UserDTO> 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<UserDTO> 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
<!-- pom.xml -->
<!-- Configuration des tests natifs -->
<plugin>
    <groupId>org.graalvm.buildtools</groupId>
    <artifactId>native-maven-plugin</artifactId>
    <configuration>
        <testArgs>
            <!-- Inclut les tests dans le build native -->
            <testArg>--verbose</testArg>
        </testArgs>
    </configuration>
    <executions>
        <execution>
            <id>test-native</id>
            <goals>
                <goal>test</goal>
            </goals>
            <phase>test</phase>
        </execution>
    </executions>
</plugin>

Prêt à réussir tes entretiens Spring Boot ?

Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.

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.

Passe à la pratique !

Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.

Tags

#graalvm
#spring boot 3
#native image
#aot compilation
#performance java

Partager

Articles similaires