# GraalVM Native Image con Spring Boot 3 nel 2026: Compilazione AOT passo passo > Guida completa per compilare applicazioni Spring Boot 3 in immagini native con GraalVM. Configurazione AOT, ottimizzazioni e deployment in produzione. - Published: 2026-03-21 - Updated: 2026-05-01 - Author: SharpSkill - Tags: graalvm, spring boot 3, native image, aot compilation, java performance - Reading time: 14 min --- La compilazione nativa con GraalVM trasforma le applicazioni Spring Boot 3 in eseguibili nativi. Il tempo di avvio passa da secondi a millisecondi e il consumo di memoria si riduce drasticamente. Questa guida copre ogni passo, dalla configurazione AOT al deployment in produzione. > **Prerequisiti** > > GraalVM 22.3+ con Native Image installato, Spring Boot 3.2+ e Maven o Gradle. La compilazione nativa richiede più RAM (8 GB minimo consigliati) e impiega diversi minuti. ## Comprendere AOT e la compilazione Native Image ### Differenza tra JIT e AOT La JVM tradizionale utilizza la compilazione Just-In-Time (JIT): il bytecode viene interpretato e poi compilato in codice macchina durante l'esecuzione. GraalVM Native Image adotta l'approccio Ahead-Of-Time (AOT): tutto il codice viene compilato prima dell'esecuzione. ```text ┌─────────────────────────────────────────────────────────────┐ │ JIT Compilation │ ├─────────────────────────────────────────────────────────────┤ │ │ │ .java → .class → JVM → Interpretation → JIT → Machine │ │ (runtime) (runtime) │ │ │ │ Advantages: Adaptive optimizations, fast class loading │ │ Disadvantages: Slow startup, high memory consumption │ └─────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────┐ │ AOT Compilation │ ├─────────────────────────────────────────────────────────────┤ │ │ │ .java → .class → GraalVM Native Image → Native executable │ │ (build time) │ │ │ │ Advantages: Instant startup, low memory footprint │ │ Disadvantages: Long build, no dynamic reflection │ └─────────────────────────────────────────────────────────────┘ ``` La compilazione AOT analizza staticamente tutto il codice raggiungibile dal punto di ingresso. Ogni codice non rilevato a tempo di compilazione viene escluso dall'immagine nativa, il che spiega i vincoli su reflection e caricamento dinamico delle classi. ### Architettura Spring AOT Spring Boot 3 integra il supporto AOT in modo nativo. Il processo di compilazione genera codice sorgente aggiuntivo che sostituisce i meccanismi dinamici con equivalenti statici. ```java // ApplicationConfig.java // Standard Spring configuration @Configuration @EnableCaching public class ApplicationConfig { @Bean public CacheManager cacheManager() { // Bean created dynamically at runtime in JIT mode // Pre-generated statically in AOT mode return new ConcurrentMapCacheManager("users", "products"); } @Bean @ConditionalOnProperty(name = "app.feature.enabled", havingValue = "true") public FeatureService featureService() { // Conditions are evaluated at build time in AOT return new FeatureServiceImpl(); } } ``` Il processo Spring AOT genera automaticamente file in `target/spring-aot/main`: ```text target/spring-aot/main/ ├── sources/ # Generated Java code │ └── com/example/ │ └── ApplicationConfig__BeanDefinitions.java ├── resources/ │ └── META-INF/ │ └── native-image/ │ ├── reflect-config.json # Reflection configuration │ ├── resource-config.json # Included resources │ └── proxy-config.json # JDK proxies ``` ## Configurazione del progetto Spring Boot ### Dipendenze Maven La configurazione Maven utilizza il plugin Spring Boot con il profilo native. Le dipendenze devono essere compatibili con 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 ``` ### Configurazione Gradle equivalente Per i progetti Gradle, la configurazione nativa è simile usando il plugin GraalVM native. ```kotlin // build.gradle.kts // Gradle configuration for Spring Boot Native plugins { java id("org.springframework.boot") version "3.4.2" id("io.spring.dependency-management") version "1.1.7" // GraalVM Native plugin 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") } // Native build configuration graalvmNative { binaries { named("main") { // Generated executable name imageName = "native-demo" // Compilation options buildArgs.addAll( "-O2", // Optimization level "--enable-http", // HTTP support "--enable-https", // HTTPS support "--verbose" // Detailed logs ) // Memory configuration for build jvmArgs.addAll("-Xmx8g") } named("test") { // Native tests with report buildArgs.add("--verbose") } } // Tracing agent for automatic discovery agent { defaultMode = "standard" enabled = true } } tasks.withType { useJUnitPlatform() } ``` > **GraalVM Tracing Agent** > > Il tracing agent (`-agentlib:native-image-agent`) individua automaticamente le chiamate via reflection durante l'esecuzione. Avviare l'applicazione con l'agent, esercitare tutte le funzionalità e poi utilizzare i file di configurazione generati. ## Gestire reflection e risorse ### Configurazione manuale della reflection Alcune librerie usano la reflection in modi non rilevabili dall'analisi statica. La configurazione manuale diventa necessaria. ```json // src/main/resources/META-INF/native-image/reflect-config.json // Configuration for classes requiring reflection [ { "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"] } ] } ] ``` ### Uso di Spring RuntimeHints Spring Boot 3 fornisce un'API programmatica per dichiarare hint nativi, più mantenibile dei file JSON. ```java // NativeHintsRegistrar.java // Programmatic registration of native hints @Configuration @ImportRuntimeHints(NativeHintsRegistrar.AppRuntimeHints.class) public class NativeHintsRegistrar { static class AppRuntimeHints implements RuntimeHintsRegistrar { @Override public void registerHints(RuntimeHints hints, ClassLoader classLoader) { // Register classes for reflection hints.reflection() // JPA entities with all members .registerType(User.class, MemberCategory.values()) .registerType(Order.class, MemberCategory.values()) // DTOs with constructors and getters/setters .registerType(UserDTO.class, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.DECLARED_FIELDS ); // Register resources to include hints.resources() // Configuration files .registerPattern("application*.yml") .registerPattern("application*.properties") // Templates and static files .registerPattern("templates/*") .registerPattern("static/**/*") // Validation messages .registerPattern("ValidationMessages*.properties"); // Register JDK proxies hints.proxies() .registerJdkProxy( UserRepository.class, Repository.class ); // Serialization for caching hints.serialization() .registerType(User.class) .registerType(ArrayList.class); } } } ``` ```java // EntityRuntimeHints.java // Automatic hints for JPA entities @Component public class EntityRuntimeHints implements RuntimeHintsRegistrar { @Override public void registerHints(RuntimeHints hints, ClassLoader classLoader) { // Automatic scan of entities in 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()); // Register each entity for full reflection hints.reflection().registerType( entityClass, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.DECLARED_FIELDS ); } catch (ClassNotFoundException e) { // Log error without interrupting build System.err.println("Entity class not found: " + bd.getBeanClassName()); } } } } ``` ## Compilazione e ottimizzazione dell'immagine nativa ### Comandi di build La compilazione nativa si effettua con Maven o Gradle. Il processo richiede diversi minuti e consuma risorse importanti. ```bash # Maven build with native profile # Generates executable in target/ mvn -Pnative native:compile # Gradle build # Generates executable in build/native/nativeCompile/ ./gradlew nativeCompile # Build with native tests included mvn -Pnative native:compile -DskipTests=false # Build with tracing agent enabled mvn -Pnative -Dagent=true test mvn -Pnative native:compile ``` ### Opzioni avanzate di ottimizzazione Le opzioni di compilazione influenzano dimensione dell'immagine, tempo di avvio e prestazioni a 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 // Build time initialization to reduce startup @Configuration public class BuildTimeInitializer { // These configurations are evaluated at build time // not at runtime static { // Initialize loggers at build time LoggerFactory.getLogger(BuildTimeInitializer.class); } @Bean @NativeHint(options = "--initialize-at-build-time=com.example.Constants") public ConstantsProvider constantsProvider() { // Constants are computed once at build return new ConstantsProvider(); } } ``` ### Confronto delle prestazioni I guadagni di prestazioni con la compilazione nativa sono significativi. ```text ┌─────────────────────────────────────────────────────────────────────┐ │ JIT vs Native Comparison │ ├─────────────────────┬─────────────────┬─────────────────────────────┤ │ Metric │ JIT (JVM) │ Native (GraalVM) │ ├─────────────────────┼─────────────────┼─────────────────────────────┤ │ Startup time │ 2.5 - 5 sec │ 50 - 200 ms │ │ RSS Memory │ 200 - 400 MB │ 50 - 100 MB │ │ Executable size │ JAR ~30 MB │ Binary ~80 MB │ │ First request time │ 100 - 500 ms │ < 10 ms │ │ Peak throughput │ Excellent │ Good (85-95% of JIT) │ │ Build time │ 30 sec │ 3 - 10 min │ └─────────────────────┴─────────────────┴─────────────────────────────┘ ``` > **Prestazioni di picco** > > Il throughput massimo in modalità nativa può essere leggermente inferiore alla modalità JIT perché le ottimizzazioni adattive del JIT non sono disponibili. Per carichi con elevate prestazioni sostenute, valutare entrambe le modalità. ## Risolvere i problemi comuni ### Errori di reflection L'errore più frequente riguarda una reflection non dichiarata. L'eccezione indica la classe mancante. ```java // ReflectionErrorHandler.java // Diagnosing and resolving reflection errors @Component @Slf4j public class ReflectionErrorHandler { // Typical error: // java.lang.ClassNotFoundException: com.example.SomeClass // when accessing via reflection // Solution 1: Add manual configuration // src/main/resources/META-INF/native-image/reflect-config.json // Solution 2: Use @RegisterReflection annotation @RegisterReflection(classes = { SomeClass.class, AnotherClass.class }) public void configureReflection() { // Annotated classes will be available for reflection } // Solution 3: Programmatic RuntimeHints public void registerHints(RuntimeHints hints) { hints.reflection().registerType( SomeClass.class, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS ); } } ``` ### Risorse mancanti I file di risorse devono essere dichiarati esplicitamente per essere inclusi nell'immagine nativa. ```json // src/main/resources/META-INF/native-image/resource-config.json // Configuration for resources to include { "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 // Programmatic resource configuration @Configuration @ImportRuntimeHints(ResourceHintsConfig.ResourceHints.class) public class ResourceHintsConfig { static class ResourceHints implements RuntimeHintsRegistrar { @Override public void registerHints(RuntimeHints hints, ClassLoader classLoader) { // YAML/Properties files hints.resources() .registerPattern("application*.yml") .registerPattern("application*.properties"); // Thymeleaf templates hints.resources().registerPattern("templates/**"); // Flyway SQL scripts hints.resources().registerPattern("db/migration/*.sql"); // Static files hints.resources().registerPattern("static/**"); // Message bundles hints.resources().registerResourceBundle("messages"); hints.resources().registerResourceBundle("ValidationMessages"); } } } ``` ### Problemi con i proxy I proxy JDK e CGLIB richiedono configurazione specifica per funzionare in modalità nativa. ```java // ProxyConfiguration.java // Managing proxies for native compilation @Configuration public class ProxyConfiguration implements RuntimeHintsRegistrar { @Override public void registerHints(RuntimeHints hints, ClassLoader classLoader) { // JDK proxies for Spring Data interfaces hints.proxies().registerJdkProxy( UserRepository.class, Repository.class, CrudRepository.class ); // Proxies for service interfaces hints.proxies().registerJdkProxy( PaymentService.class, TransactionalService.class ); } // Alternative: force CGLIB proxies @Bean public BeanFactoryPostProcessor forceProxyTargetClass() { return beanFactory -> { // Use CGLIB instead of JDK proxies // More compatible with native compilation }; } } ``` ## Deployment Docker e Kubernetes ### Dockerfile multi-stage ottimizzato La build multi-stage separa la compilazione dall'esecuzione per ottenere un'immagine minima. ```dockerfile # Dockerfile # Multi-stage build for Spring Boot Native # Stage 1: Build with GraalVM FROM ghcr.io/graalvm/graalvm-community:21 AS builder # Install Native Image RUN gu install native-image WORKDIR /app # Copy build files COPY pom.xml . COPY src ./src # Install Maven RUN microdnf install -y maven # Native build with dependency caching RUN --mount=type=cache,target=/root/.m2 \ mvn -Pnative native:compile -DskipTests # Stage 2: Minimal runtime image FROM gcr.io/distroless/base-debian12 WORKDIR /app # Copy native executable COPY --from=builder /app/target/native-demo /app/native-demo # Exposed port EXPOSE 8080 # Healthcheck HEALTHCHECK --interval=10s --timeout=3s --start-period=5s \ CMD ["/app/native-demo", "--health"] # Execution ENTRYPOINT ["/app/native-demo"] ``` ```dockerfile # Dockerfile.alpine # Alternative with Alpine for even smaller image 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 # Minimal Alpine image (< 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"] ``` ### Deployment Kubernetes con risorse ottimizzate Le applicazioni native richiedono meno risorse rispetto alle applicazioni JVM tradizionali. ```yaml # kubernetes/deployment.yaml # Optimized Kubernetes deployment for 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 # Reduced resources thanks to native resources: requests: memory: "64Mi" # vs 256Mi for JVM cpu: "50m" # vs 200m for JVM limits: memory: "128Mi" # vs 512Mi for JVM cpu: "200m" # vs 500m for JVM # Fast probes (instant startup) readinessProbe: httpGet: path: /actuator/health/readiness port: 8080 initialDelaySeconds: 1 # vs 30s for JVM periodSeconds: 5 failureThreshold: 3 livenessProbe: httpGet: path: /actuator/health/liveness port: 8080 initialDelaySeconds: 2 # vs 60s for JVM periodSeconds: 10 failureThreshold: 3 # Environment variables env: - name: SPRING_PROFILES_ACTIVE value: "production" - name: JAVA_TOOL_OPTIONS value: "" # No JVM options needed --- 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 rapido** > > Il tempo di avvio istantaneo permette uno scaling orizzontale molto rapido. I nuovi pod sono pronti in pochi secondi, ideale per carichi con picchi di traffico. ## Test e validazione dell'immagine nativa ### Configurazione dei test nativi I test possono anche essere compilati ed eseguiti in modalità nativa per validare il comportamento. ```java // NativeIntegrationTest.java // Integration tests for native validation @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: create a user UserDTO request = new UserDTO("John", "john@example.com"); // Act: API call ResponseEntity createResponse = restTemplate.postForEntity( "/api/users", request, UserDTO.class ); // Assert: verify creation assertThat(createResponse.getStatusCode()).isEqualTo(HttpStatus.CREATED); assertThat(createResponse.getBody()).isNotNull(); assertThat(createResponse.getBody().getName()).isEqualTo("John"); // Verify retrieval 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() { // Specific test to validate reflection configuration User user = new User(); user.setName("Test"); user.setEmail("test@example.com"); // ORM uses reflection to map entities 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 ``` ## Conclusione La compilazione nativa con GraalVM trasforma le applicazioni Spring Boot 3 in eseguibili performanti. Punti chiave: **Configurazione del progetto:** - ✅ Spring Boot 3.2+ con plugin GraalVM native - ✅ RuntimeHints per reflection e risorse - ✅ Tracing agent per la scoperta automatica **Ottimizzazioni di build:** - ✅ Opzioni di compilazione adatte (O2/O3, GC, compressione) - ✅ Inizializzazione a build time per i componenti statici - ✅ Quickbuild per lo sviluppo, build completa per la produzione **Risoluzione dei problemi:** - ✅ Configurazione esplicita della reflection per librerie esterne - ✅ Dichiarazione delle risorse da includere - ✅ Gestione dei proxy JDK e CGLIB **Deployment:** - ✅ Immagini Docker multi-stage con distroless - ✅ Risorse Kubernetes ridotte (64 Mi vs 256 Mi) - ✅ Probe con ritardi minimi (avvio istantaneo) La compilazione nativa è ideale per microservizi, funzioni serverless e ambienti con risorse limitate. L'avvio istantaneo e il basso consumo di memoria compensano ampiamente il tempo di build più lungo. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/it/blog/spring-boot/graalvm-native-image-spring-boot-3-aot-compilation