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.