---
title: "Quarkus native build troubleshooter"
type: "skill"
url: "https://useaiblocks.com/skills/prepare-a-quarkus-native-build"
updated: "2026-10-28"
first_published: "2026-10-25"
job: "Ship and host your app"
tested: true
licence: "MIT"
source: "https://github.com/github/awesome-copilot/tree/613efc8c21d9b3ce642dff361d17006cd9967e28/skills/quarkus-native-and-container"
instructions: "https://useaiblocks.com/skills/prepare-a-quarkus-native-build/SKILL.md"
---

# Quarkus native build troubleshooter

> Share your Quarkus version, target and build log. Get compatible fixes and artifact checks.

Share the Quarkus project, target platform and build logs. Match the tools to the target. Get fixes and checks for the built app.

Free skill for the job "Ship and host your app" · Level: intermediate · Thinking: medium · Tested · Updated 2026-10-28

## Links

- Page: https://useaiblocks.com/skills/prepare-a-quarkus-native-build
- Instructions (SKILL.md): https://useaiblocks.com/skills/prepare-a-quarkus-native-build/SKILL.md
- All files (zip): https://useaiblocks.com/skills/prepare-a-quarkus-native-build.zip
- JSON version: https://useaiblocks.com/skills/prepare-a-quarkus-native-build.json
- In your AI: connect https://useaiblocks.com/mcp and ask "Use AI Blocks to get prepare-a-quarkus-native-build"

## What it does

Share the Quarkus project, target platform and build logs. Match the tools to the target. Get fixes and checks for the built app.

## What you give it

- Your Quarkus project files, such as pom.xml or build.gradle
- Your Quarkus version and build tool (Maven or Gradle)
- Your target: your own machine, a Linux container, Kubernetes or OpenShift
- Your build log or runtime error (optional)

## What you get

- The right build command for your Quarkus version and target.
- A list of faults, each with cause, fix and the file or setting to change.
- Steps to test the native binary, not just the Java build.
- Container image and Kubernetes or OpenShift settings to review before you deploy.

## Example: what goes in and what comes out

### What goes in

- Your Quarkus project files, such as pom.xml or build.gradle
- Your Quarkus version and build tool (Maven or Gradle)
- Your target: your own machine, a Linux container, Kubernetes or OpenShift
- Your build log or runtime error (optional)

### Before you start

- Docker or Podman for a container build, or Mandrel or GraalVM installed for a local build
- An AI that can read your project files, or paste the key files into the chat

### What comes out

- **Build command**: The exact Maven or Gradle command and flags that fit your Quarkus version and target.
- **Native failures**: Each build or runtime fault with its cause, the fix and the file or property to change.
- **Test the native binary**: How to run the integration test against the native binary, and how to run it on a non-Linux host.
- **Container image**: Steps to add a Jib or Docker image extension and set the image name, registry and tag.
- **Kubernetes or OpenShift manifests**: The extension to add, the properties to set and which generated file to review.
- **Check list**: Checks to run on the real artifact before you deploy.

## Use it when

- Your Quarkus native build fails and you do not know why.
- Your native app builds but throws ClassNotFoundException when it runs.
- You need a container image or Kubernetes manifests for a Quarkus app.
- You are building native on a Mac or Windows PC and the binary will not start.

## Skip it when

- Your project is not Quarkus. Use the java-add-graalvm-native-image-support skill if you have it, or read the GraalVM native image docs.
- You only need a normal Java build. Run the standard Maven or Gradle package step, no native flags needed.
- You want to move to a newer Quarkus version. Use the quarkus update command from the Quarkus upgrade guide.

## How to use it

1. Open your AI tool and paste the skill text, or add the skill folder if your tool takes files.
2. Paste your pom.xml or build.gradle and say which Quarkus version you use.
3. Say where the app will run, such as a Linux container or Kubernetes.
4. Paste your build log or runtime error if you have one.
5. Read the fixes, then run the build yourself. A native build needs Docker, Podman or Mandrel on your machine and can take minutes.
6. Check the generated YAML and keep secrets out of it before you deploy.
7. Next, run the native build in CI or on demand, not on every test run. Native builds are slow and use several GB of memory. Check the Kubernetes YAML again before any deploy.

## What to do next

- [Service rollout and rollback planner](https://useaiblocks.com/skills/copilot-devops-rollout-plan): Service rollout and rollback planner

## Sources and credits

- From github: https://github.com/github/awesome-copilot/tree/613efc8c21d9b3ce642dff361d17006cd9967e28/skills/quarkus-native-and-container, shared under the MIT licence (original name: quarkus-native-and-container).
- [quarkus-native-and-container skill](https://github.com/github/awesome-copilot/tree/613efc8c21d9b3ce642dff361d17006cd9967e28/skills/quarkus-native-and-container), github: Original instructions and supporting files, with attribution and the MIT licence. Display copy describes the task.

## Instructions (SKILL.md)

## Quarkus Native and Container

**Start here:**
1. Pick the build command that fits the project's Quarkus version and target (see "Native build").
2. Run the native build, and use "Common native failures" if it fails.
3. Test the native binary, then build the container image and Kubernetes files.

The version numbers and timings in this file ("verified", "about 70 s") are the author's own results on their machines. Treat them as a guide, and confirm them on yours.

Check the project's Quarkus version first. For generic (non-Quarkus) GraalVM work, use the optional `java-add-graalvm-native-image-support` skill if installed (otherwise see the GraalVM docs, <https://www.graalvm.org/latest/reference-manual/native-image/>).

<!-- needs: written by tools/add-needs.mjs for AI Blocks; not part of the original skill. Edit that tool, not this section. -->
### Before you start
Use the material below to do this job. If facts or tool access are missing, explain what you can finish and what must wait.

**What you need** (use your own material or the linked skill):
- **A codebase** (yours): a project's code, in a folder or repo the AI can open.
- **Your question** (yours): the decision or problem, and what a good outcome looks like.

How to start:
1. **Look first.** Use what they've already attached, pasted or put in their about-me note. In a coding assistant with file access, look in the project too for these by name.
2. **Check what's missing.** Look for a codebase, your question. They can attach files, paste the details, or share a file path your assistant can read. Ask only for missing facts that change the result. If they ask you to start with what they have, complete the parts you can and mark the gaps.
3. **Do the job.** Follow the skill below with the facts and tools you have. Mark each gap with `MISSING:` where it belongs and never invent a fact to fill it.
<!-- /needs -->

### Native build

```bash
# container build: no local GraalVM needed (Docker/Podman required)
./mvnw package -Dnative -Dquarkus.native.container-build=true
# local Mandrel/GraalVM (JAVA_HOME / GRAALVM_HOME must point to it)
./mvnw package -Dnative
# Gradle, Quarkus 3.10+ (add -Dquarkus.native.container-build=true without a local Mandrel)
./gradlew build -Dquarkus.native.enabled=true -Dquarkus.package.jar.enabled=false
# Gradle, older Quarkus
./gradlew build -Dquarkus.package.type=native
```

`quarkus.package.type` was deprecated in 3.10 (the 3.10 migration guide replaces it with `quarkus.native.enabled` and `quarkus.package.jar.*`); it is still accepted in 3.40 with a warning. Version gate verified with Gradle: on 3.9.5 `-Dquarkus.native.enabled=true -Dquarkus.package.jar.enabled=false` is ignored ("Unrecognized configuration key" warnings) and only a JVM `quarkus-app` is built, while `-Dquarkus.package.type=native` produces the native runner; on 3.10.0, 3.27.6 and 3.40.1 the new keys produce the native runner (3.10.0 also accepts the old key with the deprecation warning). The Maven `native` profile in the generated `pom.xml` already sets `quarkus.native.enabled=true`, `quarkus.package.jar.enabled=false` and `skipITs=false` when `-Dnative` is passed.

Local build verified on macOS arm64 with the Mandrel tarball for your Quarkus line from <https://github.com/graalvm/mandrel/releases> (`mandrel-java25-macos-aarch64-25.0.4.1-Final.tar.gz` for 3.40.1, extracted anywhere, `JAVA_HOME` pointing at its `Contents/Home`; Xcode command line tools are enough): 3.40.1 Maven `package -Dnative` took about 70 s and Gradle `build` with the two flags about 2.5 min; the runner is a Mach-O executable that starts in about 0.04 s, and `./mvnw verify -Dnative` ran the `*IT` against it successfully. Quarkus checks the Mandrel/GraalVM version: 3.40 refuses 23.1 ("Out of date version of GraalVM or Mandrel detected ... Quarkus currently supports 25.0.0"), so use Mandrel 25 with 3.40 and `mandrel-java21-macos-aarch64-23.1.12.1-Final` with 3.27.6 (Maven `verify -Dnative` and Gradle both verified there).

Binary: `target/*-runner` (Maven) or `build/*-runner` (Gradle). A container build on macOS or Windows produces a Linux ELF that cannot run on the host: run it in a Linux container (for example `docker run -v "$PWD/target:/w:ro" registry.access.redhat.com/ubi9/ubi-minimal /w/<name>-runner` with the datasource passed through `QUARKUS_DATASOURCE_*` env vars), and note that `./mvnw verify -Dnative` then fails in the `*IT` with `Exit code is: '126'`. Mandrel is Red Hat's GraalVM distribution for Quarkus; the container build pulls a Mandrel builder image matching the project (3.40.1 used `quay.io/quarkus/ubi9-quarkus-mandrel-builder-image:jdk-25`, Mandrel 25.0.4.1, about 2.5 minutes including the image pull); override it with `quarkus.native.builder-image` if needed. Native builds are slow and memory-hungry (several GB); do them in CI or on demand, not on every test run.

### Common native failures

| Symptom | Fix |
|---|---|
| `ClassNotFoundException` / `NoSuchMethodException` at runtime (reflection, JSON serialization of DTOs) | annotate with `@RegisterForReflection` (or `@RegisterForReflection(targets = ...)` on a holder class for third-party types), or put a `reflection-config.json` in `src/main/resources` and set `quarkus.native.additional-build-args=-H:ReflectionConfigurationFiles=reflection-config.json` |
| Missing resource files | `quarkus.native.resources.includes=templates/**` |
| Library not supporting native | look for a Quarkus extension, or choose another library |
| Class initialized at build time breaks | `--initialize-at-run-time=...` through `quarkus.native.additional-build-args` |
| `OutOfMemoryError` during build | raise container memory or `quarkus.native.native-image-xmx` |
| Dynamic proxies / serialization | register via annotations or extension |

```java
@RegisterForReflection(targets = ExternalDto.class)
public record ProductDto(String name) {}
```

Prefer Quarkus extensions (they register reflection automatically) over raw libraries. Verified: a Panache + Quarkus REST app built natively and served JSON with a record DTO; Quarkus REST registers returned types itself, so `@RegisterForReflection` is mainly needed for types reached only reflectively (custom serialization, third-party types). Both rows were reproduced on 3.40.1 (local Mandrel 25): a resource calling `Class.forName("org.acme.Hidden").getDeclaredConstructor().newInstance()` returned HTTP 500 `ClassNotFoundException: org.acme.Hidden` in the native binary, and worked after adding `src/main/resources/reflection-config.json` (`[{ "name": "org.acme.Hidden", "allDeclaredConstructors": true }]`) with the documented `-H:ReflectionConfigurationFiles=reflection-config.json` (the relative file name resolves from `src/main/resources`). For the run-time initialization row, a `static final java.util.Random` in a class forced to build-time init (`--initialize-at-build-time=org.acme.Holder`) failed the build with "Detected an instance of Random/SplittableRandom class in the image heap ... mark it explicitly for run time initialization with '--initialize-at-run-time=<culprit>'"; switching to `--initialize-at-run-time=org.acme.Holder` fixed it, also combined with the reflection flag in one comma-separated `quarkus.native.additional-build-args`. Quarkus's generated native-image feature calls `RuntimeClassInitialization.initializeAtBuildTime("")` and then applies explicit run-time overrides, so classes default to build-time initialization. Your own static initializers (random seeds, clocks, env reads, threads) can therefore cause this failure too, not only libraries.

### Test the native binary

```java
@QuarkusIntegrationTest
class GreetingResourceIT extends GreetingResourceTest {}
```

```bash
./mvnw verify -Dnative
```

`@QuarkusIntegrationTest` black-box tests run in the `verify` phase (Failsafe) against the native binary; no CDI injection.

### Container images

```bash
quarkus extension add container-image-jib      # or container-image-docker
./mvnw package -Dquarkus.container-image.build=true
./mvnw package -Dnative -Dquarkus.native.container-build=true -Dquarkus.container-image.build=true
```

`container-image-jib` needs no Docker daemon to push: with `DOCKER_HOST` pointing at a non-existent socket, `./mvnw package -Dquarkus.container-image.build=true -Dquarkus.container-image.push=true -Dquarkus.container-image.registry=localhost:5055 -Dquarkus.container-image.insecure=true -Dquarkus.container-image.group=acme` pushed `acme/q340m:1.0.0-SNAPSHOT` to a local `registry:2` (3.40.1, JVM image). Without `push=true` Jib loads the image into a local Docker/Podman and fails with `Cannot get an executable name when no container runtime is available` when none is reachable. A tar-only output was not found or tested; the pushed image was not run.

Dockerfiles for manual builds live in `src/main/docker` (`Dockerfile.jvm`, `Dockerfile.native`, `Dockerfile.native-micro`). Set `quarkus.container-image.registry/group/name/tag`, and `-Dquarkus.container-image.push=true` to push. Use pinned base-image tags and run as non-root.

### Kubernetes / OpenShift

```bash
quarkus extension add kubernetes     # or: quarkus extension add openshift
./mvnw package
```

Manifests are generated in `target/kubernetes/kubernetes.yml` (and `.json`) for the `kubernetes` extension, and in `target/kubernetes/openshift.yml` for `openshift`. Customize via properties; the prefix follows the target (`quarkus.kubernetes.*` for Kubernetes, `quarkus.openshift.*` for OpenShift):

```properties
quarkus.kubernetes.replicas=2
quarkus.kubernetes.ingress.expose=true
quarkus.kubernetes.env.secrets=app-secrets
quarkus.kubernetes.resources.requests.memory=128Mi
```

The generated Deployment has no probes by default; with `quarkus-smallrye-health` installed it includes liveness, readiness and startup probes (`/q/health/live`, `/q/health/ready`, `/q/health/started`), and it sets `envFrom.secretRef` for `env.secrets` (verified on 3.40.1). Deploy with `quarkus.kubernetes.deploy=true` or `kubectl apply -f target/kubernetes/kubernetes.yml`. On OpenShift use the same properties under `quarkus.openshift.*`, expose the service with a Route via `quarkus.openshift.route.expose=true` (Kubernetes uses `quarkus.kubernetes.ingress.expose=true` instead), and apply with `oc apply -f target/kubernetes/openshift.yml` (or `quarkus.openshift.deploy=true`). Review generated YAML before applying; keep secrets out of it.

See also: native executables <https://quarkus.io/guides/building-native-image>; upgrades via `quarkus update` (<https://quarkus.io/guides/update-quarkus>); GraalVM release notes <https://www.graalvm.org/release-notes/>.

<!-- next: written by tools/add-needs.mjs for AI Blocks; not part of the original skill. Edit that tool, not this section. -->
### After this
When you've handed it back, suggest what they could do next in one short question. It's a suggestion: if they're done, that's fine. Offer these, and say they can type their own:
- The [Service rollout and rollback planner](https://useaiblocks.com/skills/copilot-devops-rollout-plan). It takes the test report and plan.
If they pick one and that skill is added here, carry straight on with what you just made. If not, give the link and say what to bring.
<!-- /next -->
