All GraalVM templates

GraalVM + Quarkus Native

Quarkus native build, Mandrel vs upstream GraalVM, and image size optimization.

DevZone Tools1,140 copiesUpdated Mar 23, 2026GraalVMQuarkusJava
# CLAUDE.md — GraalVM + Quarkus Native

## Why Quarkus + native is a sweet spot

- Quarkus is **designed** for native compilation. Most extensions ship native hints out of the box.
- Build time is fast (relative to other Java frameworks) thanks to Quarkus' build-time framework processing.
- Cold-start on Lambda / Cloud Run drops from 5+ seconds (JVM Spring) to ~50 ms (native Quarkus).

## Mandrel vs upstream GraalVM

- **Mandrel** is Red Hat's downstream of GraalVM CE — focused exclusively on `native-image`.
- Quarkus tests against Mandrel by default. Pin a specific Mandrel image:
  ```
  quarkus.native.builder-image=quay.io/quarkus/ubi-quarkus-mandrel-builder-image:23.1-java21
  ```
- Use upstream GraalVM if you need polyglot, Truffle, or Oracle's advanced JIT optimizations alongside.

## Build commands

- JVM mode: `./mvnw package` (still produces an optimized AOT artifact in Boot/Quarkus).
- Native mode: `./mvnw package -Pnative`.
- Container build (no GraalVM/Mandrel locally): `./mvnw package -Pnative -Dquarkus.native.container-build=true`.

## Configuration knobs

- `quarkus.native.additional-build-args` for niche flags.
- `quarkus.native.resources.includes` for resources you load via `getResource`.
- `quarkus.native.compression.level=10` (Quarkus 3.5+) for smaller binaries (UPX-based).
- Disable monitoring extras you don't need: `--no-fallback`, `-H:-AddAllCharsets`.

## Reflection

- Most Quarkus extensions register reflection automatically via the build-time SPI.
- For custom code: `@RegisterForReflection(targets = MyDto.class)`.
- For JSON DTOs (Jackson, JSON-B): annotation is needed if the class is loaded reflectively. Records and standard JAX-RS DTOs usually work without it.

## Image size

- Typical Quarkus native image: 50–80 MB binary, ~100 MB Docker image with distroless.
- Strip what you don't use:
  - `quarkus.native.enable-https-url-handler=false` if not needed
  - `quarkus.native.enable-jni=false` if no JNI deps
  - `quarkus.native.add-all-charsets=false` (default) — explicit charset list via `quarkus.native.include-charsets=UTF_8`

## Tests

- `@QuarkusIntegrationTest` runs against the packaged native binary. Add to CI; don't push without it.
- Use the same test classes for JVM and native — Quarkus picks the artifact based on the Maven profile.
- Slow native build means CI matrix: native tests on main; JVM-mode on PRs.

## Common pitfalls

- A library that uses `Class.forName(stringFromConfig)` at runtime — won't work in native unless every possible class is in the reachability metadata.
- Static initializers that touch the network or filesystem — they run at build time by default and can leak build-host state into the binary.
- JNI without explicit configuration — needs `jni-config.json` and the library binary in the image.

## Workflow

- Develop in JVM dev mode (`./mvnw quarkus:dev`). Live reload is invaluable.
- Run native tests on PR merges, not every push. Build is too slow for inner loop.
- Profile native binaries with `--enable-monitoring=jfr` and Java Flight Recorder.

## Don't

- Don't build native locally on every save. Use JVM dev mode.
- Don't skip `@QuarkusIntegrationTest` for native. Bugs that hide in JVM emerge in native.
- Don't pull in Spring/Quarkus mixed-stack libraries — Quarkus' native story works because every extension is on board.
- Don't deploy native binaries built on Mac for Linux containers without cross-compilation. Use the container builder image.

Other GraalVM templates