All GraalVM templates

Quarkus + GraalVM Native

Native compilation, reflection config, resource inclusion, and image-size tuning.

DevZone Tools1,180 copiesUpdated Apr 6, 2026QuarkusGraalVMJava
# 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 GraalVM templates