# Research: Native Image Deployment **Branch**: `005-native-image-deployment` | **Date**: 2026-04-07 ## Decision Log --- ### 1. GraalVM Version and Java 25 Support **Decision**: Use GraalVM JDK 25 (Oracle GraalVM or GraalVM CE 25). **Rationale**: Oracle GraalVM 25 ships with Java 25 support and includes the `native-image` tool. GraalVM CE 25 is available via sdkman (`sdk install java 25-graalce`). Both support the `native-maven-plugin` 0.10.x workflow. **Alternatives considered**: - GraalVM 21 with Java 21: would require downgrading `java.version` in pom.xml — rejected because pom.xml already targets Java 25. - Mandrel (Red Hat distribution): Red Hat Mandrel 25 may lag behind Oracle GraalVM 25 release; acceptable fallback but Oracle GraalVM 25 is preferred. --- ### 2. Native Image Build Toolchain **Decision**: Use `native-maven-plugin` (0.10.x) inside a Maven `native` profile, combined with Spring Boot's AOT processing via `spring-boot-maven-plugin` `process-aot` execution. **Rationale**: This is the canonical Spring Boot 4.x native image approach: 1. `mvn -Pnative package` runs AOT → generates source under `target/spring-aot/` 2. `native:compile` compiles the AOT-generated sources + app into a native executable. The `native-maven-plugin` coordinates with GraalVM `native-image` CLI automatically. **Alternatives considered**: - Paketo Buildpacks (`spring-boot:build-image -Pnative`): does not use Jib; rejected because the user explicitly wants Jib. - Manual `native-image` CLI: fragile, no Maven lifecycle integration — rejected. --- ### 3. Jib + Native Image Integration **Decision**: Use `jib-maven-plugin` (3.4.x) with the `jib-native-image-extension-maven` extension (0.1.0). **Rationale**: The Jib native image extension packages a pre-built native executable into a Docker image without requiring a Docker daemon. The workflow is: ``` mvn -Pnative native:compile # step 1: build native executable mvn -Pnative jib:dockerBuild # step 2: package into Docker image ``` Or combined (with `process-aot` wired in `native` profile): ``` mvn -Pnative package jib:dockerBuild ``` Jib uses the extension to locate the native executable under `target/` and set it as the container entrypoint. **Key configuration**: ```xml com.google.cloud.tools jib-maven-plugin 3.4.5 gcr.io/distroless/base-nossl-debian12 com.google.cloud.tools.jib.maven.extension.nativeimage.JibNativeImageExtension com.google.cloud.tools jib-native-image-extension-maven 0.1.0 ``` **Alternatives considered**: - Custom Dockerfile for native image: more control but manual, diverges from Jib workflow — rejected in favor of Jib as specified. - `jib:build` (push to registry) vs `jib:dockerBuild` (local daemon): default to `jib:dockerBuild` for dev; `jib:build` for CI with registry configured. --- ### 4. Docker Base Image for Native Executable **Decision**: `gcr.io/distroless/base-nossl-debian12` (Debian 12 glibc variant). **Rationale**: GraalVM native images compiled on Linux link against glibc by default. Distroless provides glibc without a shell, minimizing attack surface. Important: the native executable compiled on the host must target the same glibc ABI as the base image. Since the build is on Linux x86_64 this matches Debian 12's glibc 2.36. **Alternatives considered**: - `alpine` (musl libc): requires `--static` or `--libc=musl` native-image flag; more complex — rejected for simplicity (KISS). - `gcr.io/distroless/java-base`: includes JVM libs we don't need — rejected (pure native). - `scratch`: too minimal; requires fully static binary with all libs included — rejected. --- ### 5. Spring AI 2.0.0-M4 Native Image Compatibility **Decision**: Rely on Spring AI's built-in AOT hints; add manual `RuntimeHints` only for gaps found during build. **Rationale**: Spring AI 2.0.0-M4 includes `@NativeHint` registrations for the OpenAI integration (HTTP client, response DTOs) and pgvector store. Spring Boot's AOT processor picks these up automatically. M4 milestone has known native image improvements. **Known gaps** (require manual RuntimeHints): - `spring-ai-pdf-document-reader`: PDFBox font/resource loading uses `Class.forName()` and classpath resource scanning. Need to register font resources and PDFBox parser classes. - `spring-ai-advisors-vector-store`: generally AOT-compatible via Spring AI hints. **Approach**: Add a `NativeHintsConfig` class annotated with `@ImportRuntimeHints` that registers PDFBox fonts and any other gaps found during a `-Ob` (quick) native build. --- ### 6. PDFBox Reflection Hints **Decision**: Register PDFBox font resources and parser classes via a `RuntimeHintsRegistrar`. **Rationale**: PDFBox 3.x loads fonts from classpath resources (`/org/apache/pdfbox/resources/`) and uses reflection for CMap/encoding classes. Without hints the native build fails at runtime with `ClassNotFoundException` or missing resource errors. **Minimum hints needed**: ```java hints.resources().registerPattern("org/apache/pdfbox/resources/**"); hints.reflection().registerType(org.apache.pdfbox.pdmodel.font.encoding.GlyphList.class, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS); // Additional classes as discovered during smoke tests ``` --- ### 7. AWS SDK v2 Native Image Support **Decision**: Add `software.amazon.awssdk:aws-crt-client` is NOT needed; instead add `software.amazon.awssdk:netty-nio-client` or use URL connection client. Add AWS SDK v2 native image support via `aws-sdk-java-v2-native-bridge` if available, otherwise register reflection manually. **Rationale**: AWS SDK v2 2.30.x provides native image support via: - `software.amazon.awssdk:apache-client` or `url-connection-client` (simpler, fewer reflection needs than netty) - The SDK's `SdkPojo` interfaces generate reflection entries; Spring Boot AOT may not cover all. **Minimum approach**: Switch S3 HTTP client from default (netty) to `url-connection-client` for simplicity in native mode, register `SdkPojo` subtypes via `RuntimeHints`. **Alternative**: If S3 is used infrequently and only for figure uploads, mark S3Client as a conditional bean initialized lazily — reflection issues then surface only on first use (not startup), making native hints easier to discover iteratively. --- ### 8. Flyway in Native Mode **Decision**: No special configuration needed; Flyway 10.x has native image support. **Rationale**: Spring Boot 4.x AOT includes Flyway native hints. PostgreSQL JDBC driver and Flyway 10 have been tested with GraalVM native. No manual hints required. --- ### 9. Spring Security in Native Mode **Decision**: No special configuration needed. **Rationale**: Spring Security 7.x (bundled with Spring Boot 4.x) includes AOT-compatible configuration. HTTP Basic auth does not require dynamic proxies at runtime. --- ### 10. Build Command Summary ```bash # Install GraalVM 25 sdk install java 25-graalce # Build native executable + Docker image (local daemon) cd backend mvn -Pnative package jib:dockerBuild # Or push to registry (CI) mvn -Pnative package jib:build \ -Djib.to.image=ghcr.io/your-org/ai-teacher-backend:native-latest \ -Djib.to.auth.username=$CI_USER \ -Djib.to.auth.password=$CI_TOKEN ``` --- ### 11. Maven Profile Structure ```xml native org.springframework.boot spring-boot-maven-plugin process-aot process-aot org.graalvm.buildtools native-maven-plugin 0.10.6 add-reachability-metadata add-reachability-metadata compile compile-no-fork package ai-teacher-backend --initialize-at-build-time=org.slf4j -H:+ReportExceptionStackTraces ```