GraalVM Native Image Rules
Native-image build, reflection config, resource inclusion, and runtime initialization.
# CLAUDE.md — GraalVM Native Image
## What native image gives you
- Fast startup (10–100ms vs 1–5s on JVM).
- Lower memory (often 1/3 to 1/5 of JVM).
- A single self-contained binary — no JRE on the target.
## What it costs you
- Slow build (1–5 minutes).
- No reflection except what's registered ahead of time.
- No dynamic class loading at runtime.
- Closed-world assumption: every code path must be reachable from `main`.
Use it for serverless, CLIs, scale-to-zero. Avoid it for throughput-bound long-running services where JIT eventually wins.
## Distributions
- **GraalVM Community Edition (CE)** — open source, MIT.
- **Mandrel** — Red Hat's downstream of GraalVM CE, focused on native-image; pairs well with Quarkus.
- **Oracle GraalVM (formerly Enterprise)** — adds JIT optimizations (G1 GC, advanced compiler). Free for production now.
## Build basics
- Maven: `org.graalvm.buildtools:native-maven-plugin`.
- Gradle: `org.graalvm.buildtools.native` plugin.
- Run: `mvn -Pnative package` or `gradle nativeCompile`.
- For a Docker build: `mvn -Pnative -Dquarkus.native.container-build=true` (Quarkus) or use the official `gcr.io/distroless/static`.
## Reflection config
Native image needs to know at build time which classes use reflection. Two ways:
1. **Tracing agent (preferred)** — run your app on the JVM with `-agentlib:native-image-agent=config-output-dir=...`, then bundle the generated config:
```sh
java -agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image/ -jar app.jar
```
2. **Manual hints** — add `reflect-config.json`, `resource-config.json` files in `META-INF/native-image/`.
## Resources
- Files accessed via `getResource` / `getResourceAsStream` need to be in `resource-config.json`:
```json
{ "resources": [{ "pattern": "config/.*\\.properties" }] }
```
- Or use the `@RegisterResource` annotation if your framework supports it.
## Class initialization
- Default policy: classes initialize at **runtime**.
- Some classes need to initialize at **build time** for performance — frameworks declare this via `--initialize-at-build-time=...`.
- Framework integrations (Spring, Quarkus, Micronaut) handle this for you. Custom code rarely needs to opt in.
## Image size
- Strip unused code: `--no-fallback`, `--gc=epsilon` (only for short-lived workloads), `-H:+RemoveUnusedSymbols`.
- Compress the binary: `--enable-monitoring=heapdump,jfr` only when needed (each adds size).
- For Docker, use `gcr.io/distroless/static` (~2 MB) as the runtime base.
## Debugging
- Build with `-g` for debug symbols: `--debug-info`.
- Profile with **JFR** (`-XX:+FlightRecorder` works on native).
- For deep diagnosis, run on the JVM and reproduce — native errors are often "this didn't work in JVM either, you just got lucky".
## Don't
- Don't expect every Java library to work in native. Test the integration in native mode early.
- Don't skip the tracing agent run — manual reflection config is tedious and error-prone.
- Don't disable build-time initialization to "fix" a class init bug. Find the actual cause.
- Don't ship `--report-unsupported-elements-at-runtime` to production. It hides bugs.
Other Java templates
Modern Java Rules
Java 21+ defaults: records, sealed types, pattern matching, virtual threads, var.
Java Testing with JUnit 5
JUnit 5 + AssertJ + Mockito + Testcontainers — opinionated test conventions.
Java Virtual Threads & Concurrency
Project Loom virtual threads, structured concurrency, and modern concurrent patterns.
Java Build (Maven & Gradle)
Build conventions, dependency management, multi-module projects, and CI patterns.
Java Streams & Collections
Streams API, immutable collections, Collectors, and idiomatic data manipulation.