# GraalVM Native Image dengan Spring Boot 3 di 2026: Kompilasi AOT Langkah demi Langkah > Panduan lengkap untuk mengompilasi aplikasi Spring Boot 3 menjadi native image dengan GraalVM. Konfigurasi AOT, optimasi, dan deployment produksi. - 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 --- Kompilasi native dengan GraalVM mengubah aplikasi Spring Boot 3 menjadi file eksekusi native. Waktu startup berkurang dari hitungan detik menjadi milidetik, dan konsumsi memori turun drastis. Panduan ini mencakup setiap langkah, mulai dari konfigurasi AOT hingga deployment di lingkungan produksi. > **Prasyarat** > > GraalVM 22.3+ dengan Native Image terpasang, Spring Boot 3.2+, serta Maven atau Gradle. Kompilasi native membutuhkan RAM lebih besar (minimum 8 GB direkomendasikan) dan memakan waktu beberapa menit. ## Memahami AOT dan kompilasi Native Image ### Perbedaan antara JIT dan AOT JVM tradisional menggunakan kompilasi Just-In-Time (JIT): bytecode diinterpretasikan lalu dikompilasi menjadi kode mesin saat runtime. GraalVM Native Image mengadopsi pendekatan Ahead-Of-Time (AOT): seluruh kode dikompilasi sebelum dijalankan. ```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 │ └─────────────────────────────────────────────────────────────┘ ``` Kompilasi AOT menganalisis seluruh kode yang dapat dijangkau dari titik masuk secara statis. Setiap kode yang tidak terdeteksi pada saat build dikecualikan dari native image, sehingga muncul batasan terkait reflection dan dynamic class loading. ### Arsitektur Spring AOT Spring Boot 3 mengintegrasikan dukungan AOT secara native. Proses kompilasi menghasilkan kode sumber tambahan yang menggantikan mekanisme dinamis dengan padanan statis. ```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(); } } ``` Proses Spring AOT secara otomatis menghasilkan file di `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 ``` ## Konfigurasi proyek Spring Boot ### Dependensi Maven Konfigurasi Maven menggunakan plugin Spring Boot dengan profil native. Dependensi harus kompatibel dengan 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 ``` ### Konfigurasi Gradle yang setara Untuk proyek Gradle, konfigurasi native dilakukan dengan plugin GraalVM native dengan cara serupa. ```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** > > Tracing agent (`-agentlib:native-image-agent`) menemukan panggilan reflection secara otomatis saat runtime. Jalankan aplikasi dengan agent, gunakan seluruh fitur, lalu manfaatkan file konfigurasi yang dihasilkan. ## Mengelola reflection dan resource ### Konfigurasi reflection manual Beberapa pustaka memakai reflection dengan cara yang tidak dapat dideteksi oleh analisis statis. Konfigurasi manual menjadi keharusan. ```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"] } ] } ] ``` ### Memanfaatkan Spring RuntimeHints Spring Boot 3 menyediakan API programatik untuk mendeklarasikan native hints, lebih mudah dipelihara dibanding 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()); } } } } ``` ## Kompilasi dan optimasi native image ### Perintah build Kompilasi native dilakukan dengan Maven atau Gradle. Proses memakan waktu beberapa menit dan menggunakan banyak resource. ```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 ``` ### Opsi optimasi lanjutan Opsi kompilasi memengaruhi ukuran image, waktu startup, dan performa 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(); } } ``` ### Perbandingan performa Kenaikan performa dari kompilasi native sangat berarti. ```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 │ └─────────────────────┴─────────────────┴─────────────────────────────┘ ``` > **Performa puncak** > > Throughput maksimum di mode native dapat sedikit lebih rendah dibandingkan mode JIT karena optimasi adaptif JIT tidak tersedia. Untuk beban dengan performa tinggi yang berkesinambungan, lakukan evaluasi pada kedua mode. ## Mengatasi masalah umum ### Kesalahan reflection Kesalahan paling sering muncul karena reflection yang tidak dideklarasikan. Exception menunjukkan kelas yang hilang. ```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 ); } } ``` ### Resource yang hilang File resource harus dideklarasikan secara eksplisit agar masuk ke dalam native image. ```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"); } } } ``` ### Masalah pada proxy Proxy JDK dan CGLIB membutuhkan konfigurasi khusus agar dapat berjalan di mode native. ```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 dan Kubernetes ### Dockerfile multi-stage yang dioptimalkan Build multi-stage memisahkan kompilasi dari eksekusi sehingga menghasilkan image yang minimal. ```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 dengan resource yang dioptimalkan Aplikasi native membutuhkan lebih sedikit resource dibanding aplikasi JVM tradisional. ```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 cepat** > > Waktu startup yang instan memungkinkan scaling horizontal sangat cepat. Pod baru siap dalam hitungan detik, ideal untuk beban dengan lonjakan trafik. ## Pengujian dan validasi native image ### Konfigurasi pengujian native Pengujian dapat dikompilasi dan dijalankan dalam mode native untuk memvalidasi perilaku. ```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 ``` ## Kesimpulan Kompilasi native dengan GraalVM mengubah aplikasi Spring Boot 3 menjadi file eksekusi berkinerja tinggi. Poin penting: **Konfigurasi proyek:** - ✅ Spring Boot 3.2+ dengan plugin GraalVM native - ✅ RuntimeHints untuk reflection dan resource - ✅ Tracing agent untuk pendeteksian otomatis **Optimasi build:** - ✅ Opsi kompilasi yang sesuai (O2/O3, GC, kompresi) - ✅ Inisialisasi pada build time untuk komponen statis - ✅ Quickbuild untuk pengembangan, build penuh untuk produksi **Pemecahan masalah:** - ✅ Konfigurasi reflection eksplisit untuk pustaka pihak ketiga - ✅ Deklarasi resource yang harus disertakan - ✅ Pengelolaan proxy JDK dan CGLIB **Deployment:** - ✅ Image Docker multi-stage berbasis distroless - ✅ Resource Kubernetes lebih hemat (64 Mi vs 256 Mi) - ✅ Probe dengan delay minimum (startup instan) Kompilasi native ideal untuk microservices, fungsi serverless, dan lingkungan dengan resource terbatas. Startup instan dan konsumsi memori rendah jauh melebihi kompensasi untuk waktu build yang lebih lama. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/id/blog/spring-boot/graalvm-native-image-spring-boot-3-aot-compilation