# Implementation Plan: Native Image Deployment **Branch**: `005-native-image-deployment` | **Date**: 2026-04-07 | **Spec**: [spec.md](spec.md) **Input**: Feature specification from `/specs/005-native-image-deployment/spec.md` ## Summary Add a `native` Maven profile to the backend that compiles the Spring Boot 4.0.5 application to a GraalVM 25 native executable, then packages it into a minimal Docker image via Jib. The JVM build (default profile + existing Dockerfile) remains unchanged. A companion `docker-compose.native.yml` enables local full-stack testing with the native image. README.md is updated with build instructions and an updated architecture diagram. ## Technical Context **Language/Version**: Java 25 (backend), TypeScript / Node 20 (frontend) **Primary Dependencies**: Spring Boot 4.0.5, Spring AI 2.0.0-M4, `native-maven-plugin` 0.10.6, `jib-maven-plugin` 3.4.5, `jib-native-image-extension-maven` 0.1.0 **Storage**: PostgreSQL 16 + pgvector (unchanged) **Testing**: Spring Boot Test / JUnit 5 (unchanged); native integration test via smoke-test on produced Docker image **Target Platform**: Linux x86_64 container (Docker), GraalVM 25 CE or Oracle GraalVM 25 **Project Type**: Web application — backend API + frontend client (Option 2 structure) **Performance Goals**: Backend container starts in < 1 s; idle RSS < 150 MB **Constraints**: Native profile is opt-in; JVM mode unchanged; no new runtime dependencies; cross-compilation out of scope **Scale/Scope**: Single backend deployable unit (1 Docker image); frontend unchanged ## Constitution Check *GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* | Principle | Status | Notes | |-----------|--------|-------| | I. KISS | ✅ Pass | Build-time change only; no new runtime layers or abstractions. | | II. Easy to Change | ✅ Pass | `native` profile is additive; default JVM profile unchanged. Native hints isolated in one `NativeHintsConfig` class. | | III. Web-First Architecture | ✅ Pass | REST API contract unchanged. | | IV. Documentation as Architecture | ⚠️ Required | README.md MUST be updated with native build instructions and updated Mermaid diagram showing the build pipeline. | | Technology Constraints | ✅ Pass | Still 1 backend + 1 frontend deployable unit; no new services. | **Post-design re-check**: After Phase 1 — Principle IV gate requires README update in same PR. ## Project Structure ### Documentation (this feature) ```text specs/005-native-image-deployment/ ├── plan.md # This file ├── spec.md # Feature specification ├── research.md # Phase 0 — toolchain & compatibility decisions ├── data-model.md # Phase 1 — no new entities (confirmed) ├── quickstart.md # Phase 1 — developer build guide ├── contracts/ │ └── build-contract.md # Build inputs/outputs & unchanged REST API └── tasks.md # Phase 2 output (/speckit.tasks — NOT created here) ``` ### Source Code (repository root) ```text backend/ ├── pom.xml # +native profile, +Jib plugin, +NativeHintsConfig ├── src/ │ └── main/ │ └── java/com/aiteacher/ │ └── config/ │ └── NativeHintsConfig.java # NEW — RuntimeHints for PDFBox, AWS SDK gaps └── src/ └── main/ └── resources/ └── META-INF/ └── native-image/ # Optional: manual JSON hints if AOT gaps remain docker-compose.yml # UNCHANGED (JVM mode) docker-compose.native.yml # NEW — native image + postgres full stack README.md # UPDATED — build instructions + Mermaid diagram ``` **Structure Decision**: Follows Option 2 (web application). Only backend is modified; frontend is untouched. New files are minimal: one Java config class, one docker-compose file. ## Complexity Tracking > No constitution violations. No entry required. --- ## Implementation Phases ### Phase 1: Maven `native` Profile + Native Maven Plugin **Goal**: `mvn -Pnative package` produces a native executable `target/ai-teacher-backend`. **Tasks**: 1. Add `` to `backend/pom.xml`: - Wire `spring-boot-maven-plugin` `process-aot` goal to `prepare-package` phase. - Add `native-maven-plugin` 0.10.6 with `add-reachability-metadata` and `compile-no-fork` executions. - Set `ai-teacher-backend` and add essential build args: `-H:+ReportExceptionStackTraces`, `--initialize-at-build-time=org.slf4j`. 2. Create `backend/src/main/java/com/aiteacher/config/NativeHintsConfig.java`: - Implements `RuntimeHintsRegistrar`. - Registers PDFBox classpath resources: `org/apache/pdfbox/resources/**`. - Registers PDFBox reflection entries for font/encoding classes. - Registers AWS SDK `SdkPojo` subtypes used by S3 (discovered iteratively). - Annotate main application class or a `@Configuration` class with `@ImportRuntimeHints(NativeHintsConfig.class)`. 3. Validate: run `mvn -Pnative package -DskipTests` and confirm executable is produced. **Acceptance**: `target/ai-teacher-backend` exists, `./target/ai-teacher-backend --help` exits cleanly, basic startup reaches DB connection stage. --- ### Phase 2: Jib Plugin — Package Native Image into Docker **Goal**: `mvn -Pnative package jib:dockerBuild` produces a local Docker image. **Tasks**: 1. Add `jib-maven-plugin` 3.4.5 to the `` section (outside native profile, but configured to package the native executable when the profile is active): - Add `jib-native-image-extension-maven` 0.1.0 as a plugin dependency. - Configure `gcr.io/distroless/base-nossl-debian12`. - Set `ai-teacher-backend` (overridable via `-Djib.to.image`). - Add `` pointing to `JibNativeImageExtension`. - Set `8080`. 2. Verify Jib picks up the native executable from `target/ai-teacher-backend` and sets it as entrypoint. 3. Run `mvn -Pnative package jib:dockerBuild` and confirm: - Image exists in local Docker: `docker images | grep ai-teacher-backend`. - Container starts: `docker run --rm -e SPRING_DATASOURCE_URL=... ai-teacher-backend`. **Acceptance**: Image starts, logs show "Started AiTeacherApplication in < 1.0 seconds", and `GET /api/v1/books` returns 200 (with DB connected). --- ### Phase 3: Integration Smoke Test **Goal**: All existing features work in native mode. **Tasks**: 1. Start PostgreSQL locally (or via existing docker-compose.yml). 2. Start the native container with full environment variables. 3. Verify: - Flyway migrations run successfully at startup. - `POST /api/v1/books` (PDF upload + embedding) succeeds. - `POST /api/v1/chat` (RAG chat) returns a non-empty answer. - Spring Security HTTP Basic auth is enforced. 4. Fix any `MissingResourceException` or `ClassNotFoundException` by adding entries to `NativeHintsConfig` and rebuilding. **Acceptance**: All 5 REST API endpoints respond correctly with native image (FR-005). --- ### Phase 4: Docker Compose for Native Stack **Goal**: `docker compose -f docker-compose.native.yml up` starts the full stack. **Tasks**: 1. Create `docker-compose.native.yml` at repo root: - `postgres` service: same as `docker-compose.yml` (copy). - `backend` service: uses `ai-teacher-backend:latest` (the Jib-built native image). - Wires all required env vars via `.env` file reference or inline secrets. - Adds `depends_on` with health check for postgres. 2. Document the `.env` file format in `quickstart.md`. **Acceptance**: `docker compose -f docker-compose.native.yml up` → all services healthy → app responds at `http://localhost:8080/api/v1/books`. --- ### Phase 5: README Update (Constitution IV Gate) **Goal**: README.md reflects the new build pipeline and updated deployment diagram. **Tasks**: 1. Add "Native Image Build" section to README.md with: - Prerequisite: GraalVM 25 install instructions (sdkman command). - Build command: `mvn -Pnative package jib:dockerBuild`. - Run command: `docker compose -f docker-compose.native.yml up`. 2. Update the Mermaid architecture diagram in README.md to show: - Build pipeline: Maven native profile → GraalVM native-image → Jib → Docker image. - Keep the runtime diagram unchanged (same components, just a lighter container). **Acceptance**: README.md Mermaid diagram renders correctly on GitHub; build section contains all commands from `quickstart.md`. --- ## Risk Log | Risk | Likelihood | Mitigation | |------|------------|------------| | Spring AI 2.0.0-M4 missing native hints for some AI response types | Medium | Iterative hint discovery during Phase 3 smoke test | | PDFBox font loading fails at runtime in native | Medium | Register full `org/apache/pdfbox/resources/**` in `NativeHintsConfig` | | AWS SDK S3 reflection errors | Medium | Switch to `url-connection-client` (simpler); lazy-init S3Client | | GraalVM 25 availability in CI | Low | Documented prerequisite; CI pipeline config out of scope (can be added as follow-up) | | Native build time > 5 min | Low | Acceptable for a first build; not a blocker for POC |