Quarkus Native et Container
Vérifiez d'abord la version Quarkus du projet. Pour du travail GraalVM générique (non-Quarkus), utilisez la skill optionnelle java-add-graalvm-native-image-support si installée (sinon consultez la doc GraalVM, https://www.graalvm.org/latest/reference-manual/native-image/).
Native build
# container build: pas besoin de GraalVM local (Docker/Podman requis)
./mvnw package -Dnative -Dquarkus.native.container-build=true
# local Mandrel/GraalVM (JAVA_HOME / GRAALVM_HOME doit pointer vers lui)
./mvnw package -Dnative
# Gradle, Quarkus 3.10+ (ajouter -Dquarkus.native.container-build=true sans Mandrel local)
./gradlew build -Dquarkus.native.enabled=true -Dquarkus.package.jar.enabled=false
# Gradle, anciennes versions de Quarkus
./gradlew build -Dquarkus.package.type=native
quarkus.package.type a été déprécié en 3.10 (le guide de migration 3.10 le remplace par quarkus.native.enabled et quarkus.package.jar.*); il est toujours accepté en 3.40 avec un avertissement. Vérification de version confirmée avec Gradle : en 3.9.5, -Dquarkus.native.enabled=true -Dquarkus.package.jar.enabled=false est ignoré (avertissements « Unrecognized configuration key ») et seul un JVM quarkus-app est construit, tandis que -Dquarkus.package.type=native produit le runner native; en 3.10.0, 3.27.6 et 3.40.1, les nouvelles clés produisent le runner native (3.10.0 accepte aussi l'ancienne clé avec l'avertissement de dépréciation). Le profil Maven native dans le pom.xml généré définit déjà quarkus.native.enabled=true, quarkus.package.jar.enabled=false et skipITs=false quand -Dnative est passé.
Build local vérifié sur macOS arm64 avec la tarball Mandrel pour votre ligne Quarkus depuis https://github.com/graalvm/mandrel/releases (mandrel-java25-macos-aarch64-25.0.4.1-Final.tar.gz pour 3.40.1, extrait n'importe où, JAVA_HOME pointant sur son Contents/Home; les outils en ligne de commande Xcode suffisent) : 3.40.1 Maven package -Dnative a pris environ 70 s et Gradle build avec les deux flags environ 2,5 min; le runner est un exécutable Mach-O qui démarre en environ 0,04 s, et ./mvnw verify -Dnative a exécuté avec succès les tests *IT contre lui. Quarkus vérifie la version Mandrel/GraalVM : 3.40 refuse 23.1 (« Out of date version of GraalVM or Mandrel detected ... Quarkus currently supports 25.0.0 »), utilisez donc Mandrel 25 avec 3.40 et mandrel-java21-macos-aarch64-23.1.12.1-Final avec 3.27.6 (Maven verify -Dnative et Gradle tous deux vérifiés là).
Binary : target/*-runner (Maven) ou build/*-runner (Gradle). Un container build sur macOS ou Windows produit un ELF Linux qui ne peut pas s'exécuter sur l'hôte : exécutez-le dans un conteneur Linux (par exemple docker run -v "$PWD/target:/w:ro" registry.access.redhat.com/ubi9/ubi-minimal /w/<name>-runner avec la datasource passée via les variables d'env QUARKUS_DATASOURCE_*), et notez que ./mvnw verify -Dnative échoue alors dans les *IT avec Exit code is: '126'. Mandrel est la distribution GraalVM de Red Hat pour Quarkus; le container build tire une image Mandrel builder correspondant au projet (3.40.1 a utilisé quay.io/quarkus/ubi9-quarkus-mandrel-builder-image:jdk-25, Mandrel 25.0.4.1, environ 2,5 minutes incluant le pull d'image); surchargez-la avec quarkus.native.builder-image si nécessaire. Les native builds sont lents et gourmands en mémoire (plusieurs GB); faites-les en CI ou à la demande, pas à chaque exécution de test.
Common native failures
| Symptôme | Solution |
|---|---|
ClassNotFoundException / NoSuchMethodException à l'exécution (réflexion, sérialisation JSON de DTOs) |
annotez avec @RegisterForReflection (ou @RegisterForReflection(targets = ...) sur une classe holder pour les types tiers), ou mettez un reflection-config.json dans src/main/resources et définissez quarkus.native.additional-build-args=-H:ReflectionConfigurationFiles=reflection-config.json |
| Fichiers ressource manquants | quarkus.native.resources.includes=templates/** |
| Bibliothèque ne supportant pas le native | cherchez une extension Quarkus, ou choisissez une autre bibliothèque |
| Classe initialisée au build time qui pose problème | --initialize-at-run-time=... via quarkus.native.additional-build-args |
OutOfMemoryError pendant le build |
augmentez la mémoire du conteneur ou quarkus.native.native-image-xmx |
| Dynamic proxies / sérialisation | enregistrez via annotations ou extension |
@RegisterForReflection(targets = ExternalDto.class)
public record ProductDto(String name) {}
Préférez les extensions Quarkus (elles enregistrent la réflexion automatiquement) aux bibliothèques brutes. Vérifié : une app Panache + Quarkus REST construite nativement et servant du JSON avec un record DTO; Quarkus REST enregistre les types retournés lui-même, donc @RegisterForReflection est principalement nécessaire pour les types atteints uniquement par réflexion (sérialisation personnalisée, types tiers). Les deux lignes ont été reproduites sur 3.40.1 (Mandrel 25 local) : une ressource appelant Class.forName("org.acme.Hidden").getDeclaredConstructor().newInstance() a retourné HTTP 500 ClassNotFoundException: org.acme.Hidden dans le binary native, et a fonctionné après ajout de src/main/resources/reflection-config.json ([{ "name": "org.acme.Hidden", "allDeclaredConstructors": true }]) avec le -H:ReflectionConfigurationFiles=reflection-config.json documenté (le nom de fichier relatif se résout depuis src/main/resources). Pour la ligne d'initialisation run-time, un static final java.util.Random dans une classe forcée à l'init build-time (--initialize-at-build-time=org.acme.Holder) a échoué le build avec « Detected an instance of Random/SplittableRandom class in the image heap ... mark it explicitly for run time initialization with '--initialize-at-run-time=<culprit>' »; passer à --initialize-at-run-time=org.acme.Holder l'a corrigé, aussi combiné avec le flag de réflexion dans un quarkus.native.additional-build-args séparé par des virgules. La native-image feature générée par Quarkus appelle RuntimeClassInitialization.initializeAtBuildTime("") puis applique les overrides run-time explicites, donc les classes sont par défaut initialisées au build-time. Vos propres initialiseurs statiques (graines aléatoires, horloges, lectures d'env, threads) peuvent donc aussi causer cet échec, pas seulement les bibliothèques.
Tester le binary native
@QuarkusIntegrationTest
class GreetingResourceIT extends GreetingResourceTest {}
./mvnw verify -Dnative
Les tests black-box @QuarkusIntegrationTest s'exécutent dans la phase verify (Failsafe) contre le binary native; pas d'injection CDI.
Container images
quarkus extension add container-image-jib # ou 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 ne nécessite pas de daemon Docker pour pusher : avec DOCKER_HOST pointant sur un socket inexistant, ./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 a pushé acme/q340m:1.0.0-SNAPSHOT vers un registry:2 local (3.40.1, image JVM). Sans push=true, Jib charge l'image dans un Docker/Podman local et échoue avec Cannot get an executable name when no container runtime is available quand aucun n'est atteignable. Une sortie tar-uniquement n'a pas été trouvée ou testée; l'image pushée n'a pas été exécutée.
Les Dockerfiles pour les builds manuels vivent dans src/main/docker (Dockerfile.jvm, Dockerfile.native, Dockerfile.native-micro). Définissez quarkus.container-image.registry/group/name/tag, et -Dquarkus.container-image.push=true pour pusher. Utilisez des tags base-image épinglés et exécutez en non-root.
Kubernetes / OpenShift
quarkus extension add kubernetes # ou : quarkus extension add openshift
./mvnw package
Les manifestes sont générés dans target/kubernetes/kubernetes.yml (et .json) pour l'extension kubernetes, et dans target/kubernetes/openshift.yml pour openshift. Personnalisez via les propriétés; le préfixe suit la cible (quarkus.kubernetes.* pour Kubernetes, quarkus.openshift.* pour OpenShift) :
quarkus.kubernetes.replicas=2
quarkus.kubernetes.ingress.expose=true
quarkus.kubernetes.env.secrets=app-secrets
quarkus.kubernetes.resources.requests.memory=128Mi
Le Deployment généré n'a pas de probes par défaut; avec quarkus-smallrye-health installé, il inclut les probes liveness, readiness et startup (/q/health/live, /q/health/ready, /q/health/started), et définit envFrom.secretRef pour env.secrets (vérifié en 3.40.1). Déployez avec quarkus.kubernetes.deploy=true ou kubectl apply -f target/kubernetes/kubernetes.yml. Sur OpenShift, utilisez les mêmes propriétés sous quarkus.openshift.*, exposez le service avec une Route via quarkus.openshift.route.expose=true (Kubernetes utilise quarkus.kubernetes.ingress.expose=true à la place), et appliquez avec oc apply -f target/kubernetes/openshift.yml (ou quarkus.openshift.deploy=true). Examinez le YAML généré avant d'appliquer; gardez les secrets en dehors.
Voir aussi : exécutables natives https://quarkus.io/guides/building-native-image; mises à jour via quarkus update (https://quarkus.io/guides/update-quarkus); notes de version GraalVM https://www.graalvm.org/release-notes/.