Die Seite nach dem Code: Was der Test macht, was der Betrieb sieht und woran man merkt, dass eine Migration wirklich fertig ist. Maßgeblich bleibt die Doku auf quarkus.io; hier stehen die Werte und Pfade, die man im Alltag nachschlägt — und die Abnahmeliste, die Versionssprünge überlebt.
Annotationen, Konfigurationsschlüssel und Panache stehen auf dem Blatt Spring Boot nach Quarkus übertragen.
Welcher Test wofür
@QuarkusTest | @QuarkusIntegrationTest | |
|---|---|---|
| Gegenstand | laufende Anwendung | gebautes Artefakt |
| Injektion | möglich | nicht möglich |
| Ausführung | Surefire | Failsafe |
| Native | nein | ja, gegen das Native Image |
Die Trennung über Surefire und Failsafe ist Pflicht — beide Arten gehören nicht in
denselben Testlauf. JUnit 5 bleibt die Grundlage, REST Assured kennt den Port
bereits, @TestHTTPResource und @TestHTTPEndpoint liefern URL und Pfad ohne
Literale, und @TestTransaction rollt einen Test nach dem Lauf zurück.
@QuarkusTest
class RueckgabeTest {
@InjectMock AbrechnungClient client;
@Test void meldetRueckgabe() {
given().body(dto).contentType(JSON)
.when().post("/rueckgaben")
.then().statusCode(201);
}
}
Ein @Singleton-Bean wird nur mit @MockitoConfig(convertScopes = true) über
@InjectMock ersetzt. Jedes zusätzliche Testprofil bedeutet einen weiteren
Anwendungsstart — das ist der übliche Grund für eine langsame Suite.
Modul: Anwendungen mit Quarkus testen
Dev Services
Gesetzt wird nur, was wirklich abweicht — ohne Angabe wählt Quarkus ein passendes Image und verdrahtet es selbst.
quarkus.devservices.enabled=true
quarkus.devservices.timeout=60S
quarkus.datasource.devservices.image-name=postgres:17
quarkus.datasource.devservices.reuse=true
quarkus.kafka.devservices.shared=true
PostgreSQL liefert das Schema, das Flyway beim Start migriert; der Kafka-Dev-Service
ersetzt den Broker; Keycloak stellt Realm und Tokens bereit. Mit shared und
service-name teilen sich mehrere Anwendungen einen Container. Eine halb gesetzte
Verbindungskonfiguration verhindert den Start des Dev Service — ohne Hinweis.
Modul: Dev Services statt manueller Testinfrastruktur
Absicherung: Spring Security zu Quarkus Security
| Spring Security | Quarkus Security |
|---|---|
@PreAuthorize mit Ausdruck | @RolesAllowed mit Rollenliste |
@Secured | @RolesAllowed |
permitAll in der Chain | @PermitAll |
denyAll in der Chain | @DenyAll |
antMatchers in der Chain | quarkus.http.auth.permission-Einträge |
Rollenbasierte Prüfungen wirken an REST-Endpunkten und an CDI-Beans; die
Zusicherung steht damit an der Methode und ist beim Lesen des Codes sichtbar.
SecurityIdentity ist die eine Quelle für Benutzer, Rollen und Attribute.
quarkus.oidc.auth-server-url=https://idp/realms/pfand
quarkus.oidc.client-id=pfandkreis
quarkus.oidc.application-type=service
quarkus.oidc.token.audience=pfandkreis
quarkus.oidc.roles.role-claim-path=realm_access/roles
Getestet wird über @TestSecurity (Benutzer, Rollen, Berechtigungen ohne echten
Provider), @OidcSecurity für Claims, den KeycloakTestClient für echte Tokens
oder OidcWiremockTestResource aus quarkus-test-oidc-server.
Modul: Von Spring Security zu Quarkus Security
Messaging: vom Listener zum Kanal
| Spring for Apache Kafka | Quarkus Messaging |
|---|---|
@KafkaListener | @Incoming auf der Methode |
KafkaTemplate.send | @Outgoing oder Emitter |
| Topic in der Annotation | Topic in der Konfiguration |
ConsumerRecord | Message mit Metadaten |
| Container-Factory | Connector smallrye-kafka |
Der Kanalname im Schlüssel ist derselbe, der in @Incoming und @Outgoing steht —
weicht er ab, bleibt der Consumer stumm.
kafka.bootstrap.servers=broker:9092
mp.messaging.incoming.rueckgaben.topic=pfand.rueckgabe
mp.messaging.incoming.rueckgaben.group.id=pfandkreis
mp.messaging.outgoing.buchungen.topic=pfand.buchung
Datenbankzugriffe in der Verarbeitung gehören mit @Blocking auf einen
Worker-Thread.
Modul: Messaging und externe Systeme
Fehlertoleranz: Vorgabewerte, die man kennen sollte
| Annotation | Vorgabe | Bedeutung |
|---|---|---|
@Retry | maxRetries 3 | drei Wiederholungen |
@Timeout | 1 Sekunde | harte Zeitgrenze |
@Bulkhead | value 10 | zehn gleichzeitige Aufrufe |
@CircuitBreaker | failureRatio 0.5 | Hälfte der Aufrufe fehlerhaft |
@CircuitBreaker | requestVolumeThreshold 20 | Beobachtungsfenster |
@CircuitBreaker | delay 5s | Wartezeit bis zum Probelauf |
Je Methode überschreibbar über
quarkus.fault-tolerance."Klasse/methode".retry.max-retries.
@Retry(maxRetries = 2)
@Timeout(2000)
@CircuitBreaker(requestVolumeThreshold = 10)
@Fallback(fallbackMethod = "ausCache")
public Konto laden(String id) { ... }
Konto ausCache(String id) { ... } // gleiche Signatur, sonst greift es nicht
Reaktiv und imperativ dürfen im selben Projekt stehen: Reaktive Endpunkte laufen
auf wenigen I/O-Threads, blockierende auf Worker-Threads. await oder toIterable
auf einem I/O-Thread schlagen bewusst fehl.
Modul: Resiliente und reaktive Anwendungen
Observability: vom Actuator zu Quarkus
| Spring Boot Actuator | Quarkus |
|---|---|
/actuator/health | /q/health |
/actuator/health/liveness | /q/health/live |
/actuator/health/readiness | /q/health/ready |
HealthIndicator | HealthCheck mit @Liveness oder @Readiness |
| Micrometer-Registry | Micrometer-Extension |
Verschiebbar über quarkus.smallrye-health.root-path. Die drei Prüfungen bleiben
getrennt, weil sie verschiedene Fragen beantworten: Liveness entscheidet über den
Neustart des Pods, Readiness über die Zuleitung von Anfragen, Startup verschiebt den
Beginn der Liveness-Prüfung beim langsamen Hochlauf. Eine Prüfung, die eine
Fremddatenbank abfragt, gehört nicht in Liveness.
Modul: Observability übertragen
Container und Kubernetes
quarkus.kubernetes.namespace=pfand
quarkus.kubernetes.replicas=3
quarkus.kubernetes.image-pull-policy=always
quarkus.kubernetes.env.vars.db-host=postgres
quarkus.kubernetes.liveness-probe.period=10S
quarkus.kubernetes.readiness-probe.period=5S
quarkus.kubernetes.deploy=true rollt beim Build direkt in den Cluster aus — ein
Schalter, der nicht versehentlich stehenbleiben sollte. Konfiguration und Secrets
kommen aus ConfigMaps und Secrets, nie aus dem Image; Graceful Shutdown lässt
laufende Anfragen zu Ende kommen.
Modul: Container und Kubernetes
JVM-Build gegen Native Build
| JVM-Build | Native Build | |
|---|---|---|
| Dauer | Sekunden | mehrere Minuten |
| Speicherbedarf | gering | mehrere Gigabyte |
| Dynamik zur Laufzeit | vollständig | nur was registriert ist |
| Test des Artefakts | JVM-Tests | Integrationstests gegen das Image |
./mvnw install -Dnative
quarkus build --native
./mvnw install -Dnative -Dquarkus.native.container-build=true
Seit Quarkus 3.36 ist GraalVM beziehungsweise Mandrel 25.0 die minimal offiziell unterstützte Version. Reflektiver Zugriff, dynamische Proxys und Ressourcen brauchen ausdrückliche Registrierung — was in der JVM funktioniert, kann nativ fehlen; umgekehrt nie.
Modul: JVM, AOT und Native Image vergleichen
Die Abnahmeliste
Erst wenn jede Zeile abgehakt ist, wird der alte Dienst abgeschaltet.
| Prüfpunkt | Nachweis |
|---|---|
| Fachliches Verhalten | Characterization Tests bestehen beidseitig |
| API-Vertrag | gleiche Statuscodes und Antwortformate |
| Fehlerfälle | gleiche Formate, gleiche Codes |
| Security | erlaubte und verbotene Zugriffe identisch |
| Messaging | gleiche Nachrichten, keine Doppelverarbeitung |
| Ressourcen | Startzeit, Speicher und Antwortzeit gemessen |
Modul: Eine Spring-Boot-Anwendung migrieren
Werkzeuge für die Umstellung
| Werkzeug | Aufgabe |
|---|---|
| Migration Toolkit for Applications | regelbasierte Analyse des Bestands |
| OpenRewrite | deterministische Codeänderungen nach Rezept |
| Quarkus Update Tool | Quarkus-Projekt auf einen neueren Stream heben |
| Konveyor AI | Analyse mit Vorschlägen in der Entwicklungsumgebung |
Für Quarkus-nach-Quarkus-Updates genügt quarkus update, wahlweise mit --stream.
Es stützt sich auf OpenRewrite-Rezepte, und die Dokumentation sagt ausdrücklich,
dass damit nur ein Teil der Migration automatisiert wird: vor dem Lauf ein eigener
Zweig, nach dem Lauf Ausgabe und Diff prüfen, dann Build und Tests.
Modul: Migrationstools, KI-Unterstützung und Roadmap
Typische Fallen
- Integrationstests landen in Surefire und starten damit die falsche Variante;
Injektion in
@QuarkusIntegrationTestschlägt fehl, weil der Prozess ein anderer ist. reuseist eingeschaltet und Tests beeinflussen sich über alte Daten.- Ohne festes Image-Tag ändert ein Testlauf unbemerkt die Datenbankversion.
- Die CI läuft ohne Container-Laufzeit und die Suite bricht erst dort ab.
- Die Audience wird nicht geprüft — fremde Tokens desselben Providers gelten damit.
- Der Rollen-Claim heißt beim Provider anders als erwartet und alle Rollen fehlen.
- Pfadregeln und Annotationen widersprechen sich, die restriktivere gewinnt unbemerkt; CORS und CSRF werden vergessen, weil sie in Spring woanders standen.
- Beide Varianten laufen mit derselben Gruppen-Kennung und teilen sich die Nachrichten.
- Eine Dead Letter Queue wird eingerichtet, aber niemandem zugewiesen.
@Retryohne@Timeoutverlängert den Ausfall, statt ihn abzufangen — und Timeout mal Wiederholungen sprengt die Zeitgrenze des Aufrufers.- Liveness prüft eine externe Abhängigkeit, und ein fremder Ausfall startet den Pod neu.
- Readiness meldet bereit, bevor Flyway die Migration beendet hat.
- Die Probes zeigen weiter auf die Actuator-Pfade und schlagen nach der Migration fehl.
- Requests und Limits bleiben aus der Spring-Boot-Zeit stehen — der Speichervorteil bleibt damit ungenutzt.
- Gemessen wird der Kaltstart, obwohl der Dienst wochenlang läuft.
Dieses Thema als Schulung für Ihr Team
Dieser Beitrag erklärt das Thema. Damit Ihr Team es danach auch anwendet, gibt es Quarkus für Spring-Boot-Teams als Schulung — an Ihrem eigenen Code, mit den Fragen, die ein Text nicht beantwortet. Sie wählen die Module, wir bauen daraus ein Programm.
3 Tage·ab 900 EUR netto pro Tag (bis 3 Teilnehmende) ·Termin nach Vereinbarung
Als Team-Schulung anfragenZum Seminar Quarkus für Spring-Boot-Teams →