Quarkus + GraalVM Native
Native compilation, reflection config, resource inclusion, and image-size tuning.
# CLAUDE.md — Quarkus + GraalVM Native
## When to go native
- Cold-start matters: serverless (Lambda, Cloud Run cold starts), CLI tools, scale-to-zero deployments.
- Memory matters: tight container sizes, edge environments.
- **Don't** go native when:
- Throughput is the priority (JIT eventually beats native after warm-up)
- The team isn't ready for the build complexity
- You depend on libraries that need extensive reflection / dynamic class loading without Quarkus extensions
## Build
- `./mvnw package -Pnative` (or `./gradlew build -Dquarkus.package.type=native`).
- Use **Mandrel** (Red Hat's GraalVM distribution focused on native-image). Pin the version: `quarkus.native.builder-image=quay.io/quarkus/ubi-quarkus-mandrel-builder-image:23.1-java21`.
- Container build: `quarkus.native.container-build=true` — works without GraalVM installed locally.
- Build is slow (1–5 minutes). Optimize CI with cached Maven repo + builder image.
## Reflection & resources
- Quarkus extensions register most reflection automatically.
- For your own code that uses reflection (rare), register classes:
```java
@RegisterForReflection(targets = { com.app.SomeDto.class })
public class ReflectionConfig {}
```
- Resources accessed at runtime: `quarkus.native.resources.includes=path/to/file.json,**/*.properties`.
- Static initializers that touch the environment (file system, network) need `--initialize-at-run-time` hints — usually handled by Quarkus.
## Runtime gotchas
- Native binaries don't support agents. JFR works but with limitations.
- Reflection on classes not registered fails at runtime — catch in tests, not prod.
- Class-loading is closed-world. No `Class.forName(dynamicString)` patterns.
## Testing
- `@QuarkusIntegrationTest` runs against the packaged artifact (jar or native).
- Use `mvn verify -Pnative` to run integration tests against the native binary in CI.
- Tag native-only tests `@DisabledOnIntegrationTest` if they only make sense in JVM mode.
## Image size
- Mandrel base image is a few hundred MB. Final native binary ~50–100 MB.
- Strip the image with `quarkus.native.compression.level=10` for binary compression.
- Use `distroless/static` or `scratch` as the runtime image — copy in the binary, run it.
## Memory
- Native uses much less heap than JVM. Set `-Xmx256m` and watch — many apps run on 64–128 MB.
- The savings come from no JIT, no class metadata, no interpreter overhead.
## Don't
- Don't switch to native to "optimize" without measuring. JVM with Quarkus already starts in 1–2s and uses ~150 MB.
- Don't expect native + reflection-heavy library to "just work". Test the integration in native mode early.
- Don't skip running the native test suite in CI. The cost is real but cheaper than a prod surprise.
- Don't use `--no-fallback` until you're sure your app works fully native — the fallback image is a JVM bundled into the binary, not what you want.
Other Quarkus templates
Modern Quarkus Rules
Quarkus 3+ defaults: dev mode, build-time optimization, configuration, and CDI.
Quarkus REST (RESTEasy Reactive)
JAX-RS endpoints with RESTEasy Reactive — request validation and response models.
Quarkus + Hibernate ORM with Panache
Active-record and repository styles with Panache, Flyway migrations, transactions.
Quarkus Testing
@QuarkusTest, dev services, mocking, and isolated test profiles.
GraalVM + Quarkus Native
Quarkus native build, Mandrel vs upstream GraalVM, and image size optimization.