Das Blatt für den Moment, in dem eine Spring-Boot-Klasse offen ist und die Quarkus-Entsprechung fehlt: Annotationen, Konfigurationsschlüssel, Abfrageformen. Die maßgebliche und stets aktuelle Doku steht auf quarkus.io — hier steht die Auswahl, die beim Übertragen wirklich gebraucht wird.
Die zweite Hälfte des Seminars — Tests, Security, Messaging, Betrieb und der Migrationsweg — liegt auf dem Blatt Quarkus testen, betreiben und migrieren.
Wofür welche Antwort spricht
Die Entscheidung ist keine Frameworkfrage, sondern eine Abwägung mit zwei möglichen Ausgängen.
| Spricht für Quarkus | Spricht für Spring Boot |
|---|---|
| Quarkus ist als Plattform bereits gesetzt | Viel Spring-spezifische Infrastruktur im Code |
| Stark skalierende, häufig startende Last | Nicht unterstützte Spring-Bibliotheken im Einsatz |
| Kubernetes- und Native-Image-Anforderungen | Kein messbarer Betriebsnutzen aus dem Umbau |
| Jakarta- und MicroProfile-APIs erwünscht | Teamkompetenz und Prozesse klar auf Spring |
Spring Boot bietet selbst AOT, Native Images und Testcontainers — allgemeine Argumente tragen die Entscheidung deshalb nicht.
Modul: Warum Quarkus für Spring-Boot-Teams
Starter und Extension
Der eine Unterschied, aus dem die meisten anderen folgen: Quarkus verlagert Arbeit vom Start in den Build.
| Spring-Boot-Starter | Quarkus Extension | |
|---|---|---|
| Wirkzeitpunkt | Start der Anwendung | Build der Anwendung |
| Mechanismus | Auto-Configuration, Bedingungen | Build Steps und Build Items |
| Ergebnis | Beans im Context | erzeugter Bytecode im Artefakt |
| Fehlerzeitpunkt | erster Start | Buildlauf |
Daraus folgt unmittelbar: @Conditional und die übrigen Conditional-Annotationen
werden ignoriert, weil die Injektion schon zur Bauzeit aufgelöst ist.
Modul: Spring Boot und Quarkus im Architekturvergleich
Vertraute Handgriffe, andere Stelle
| Spring Boot | Quarkus |
|---|---|
application.yml | application.properties |
spring.profiles.active | quarkus.profile |
@ConfigurationProperties | @ConfigMapping |
@Value | @ConfigProperty |
spring-boot-starter-web | quarkus-rest-jackson |
YAML ist über eine eigene Extension nachrüstbar, der Standardweg sind aber
Properties. Profile heißen dev, test und prod:
quarkus.http.port=8080
%dev.quarkus.http.port=8181
%test.quarkus.log.level=WARN
# QUARKUS_HTTP_PORT=9090 sticht die Datei
quarkus create app de.hco:pfandkreis --extension='rest-jackson'
quarkus extension add hibernate-orm-panache
quarkus extension list --installable
quarkus dev # Dev UI: http://localhost:8080/q/dev-ui/
Modul: Das Quarkus-Projekt aufsetzen · Warum Quarkus für Spring-Boot-Teams
Dependency Injection: Spring zu CDI
| Spring | Quarkus | Anmerkung |
|---|---|---|
@Autowired | @Inject | Listen über io.quarkus.arc.All |
@Qualifier | @Named | |
@Value | @ConfigProperty | keine Expression Language |
@Component, @Service, @Repository | @Singleton | Singleton als Vorgabe |
@Configuration | @ApplicationScoped | |
@Bean | @Produces | |
@ComponentScan | nicht unterstützt | Scan zur Bauzeit |
@Import | nicht unterstützt | kein CDI-Gegenstück |
Die Kompatibilitätsschicht kommt über quarkus extension add spring-di und ist
als Zwischenschritt gedacht, nicht als Ziel. @Autowired(required=false) deckt
sie nicht ab — dort tritt Instance an seine Stelle.
@Service // Spring-Kompatibilität
public class PfandRechner {
@Autowired RueckgabeRepository repo;
}
@ApplicationScoped // CDI
public class PfandRechner {
@Inject RueckgabeRepository repo;
}
Modul: Dependency Injection von Spring zu CDI
REST-Annotationen
| Spring Web | Jakarta REST |
|---|---|
@RestController | @Path auf der Klasse |
@GetMapping, @PostMapping | @GET, @POST mit @Path |
@PathVariable | @PathParam oder @RestPath |
@RequestParam | @QueryParam oder @RestQuery |
@RequestHeader | @HeaderParam oder @RestHeader |
@RequestBody | der Methodenparameter selbst |
@ResponseStatus | @APIResponse und Response-Status |
Die Spring-Kompatibilität deckt außerdem @MatrixVariable, @CookieValue,
@ExceptionHandler und @RestControllerAdvice ab — von letzterer allerdings
ausschließlich die Exception-Handler-Funktion. Der Application Context wird dabei
nie gestartet; die Annotationen dienen nur als Metadaten.
@Path("/becher")
class BecherResource {
@GET @Path("/{id}")
Becher holen(@RestPath String id) { ... }
@POST
Response anlegen(@Valid RueckgabeDto dto) { ... }
// Verstoß → 400 mit { "title": "Constraint Violation", "violations": [...] }
}
Das Antwortformat bestimmt ein eigener ExceptionMapper mit @Provider
vollständig; er löst damit den eingebauten Mapper der Validierung ab. OpenAPI
liefert quarkus-smallrye-openapi unter /q/openapi, die Swagger UI unter
/q/swagger-ui — letztere nur in dev und test.
Modul: REST APIs mit Quarkus entwickeln
Konfigurationsquellen und ihr Ordinal
Die höhere Zahl gewinnt.
| Quelle | Ordinal |
|---|---|
| System-Properties | 400 |
| Umgebungsvariablen | 300 |
Datei .env im Arbeitsverzeichnis | 295 |
config/application.properties neben dem Start | 260 |
application.properties im Classpath | 250 |
META-INF/microprofile-config.properties | 100 |
Für den Schlüssel foo.BAR.baz sucht Quarkus zusätzlich nach foo_BAR_baz und
FOO_BAR_BAZ. Im .env-Format heißt %dev.quarkus.http.port entsprechend
_DEV_QUARKUS_HTTP_PORT.
@ConfigMapping(prefix = "pfandkreis")
public interface PfandConfig {
int fristTage(); // → pfandkreis.frist-tage
BigDecimal pfandProBecher(); // → pfandkreis.pfand-pro-becher
}
quarkus.datasource.username=${DB_USER}
quarkus.datasource.password=${DB_PASS:leer} # Doppelpunkt = Vorgabewert
quarkus.config.profile.parent=common
Modul: Konfiguration und Profile übertragen
Persistence: Spring Data zu Panache
Was die Kompatibilitätsschicht für Spring Data JPA ausdrücklich nicht abdeckt:
| Nicht unterstützt | Konsequenz |
|---|---|
| QueryDSL | Abfragen neu schreiben |
QueryByExampleExecutor | Kriterien explizit formulieren |
JpaSpecificationExecutor | Specifications entfallen |
Native Queries in @Query | anders ausführen |
@Lock | Sperrverhalten neu lösen |
Future als Rückgabetyp | Signatur ändern |
Ebenfalls nicht abgedeckt: eigene Basis-Repositories und Entity-State über
EntityInformation.
| Spring Data | Panache |
|---|---|
findAll() | listAll() |
findById(id) | findById(id), findByIdOptional(id) |
findByStelle(s) | list("stelle", s) |
countByStelle(s) | count("stelle", s) |
save(e) | persist(), persistAndFlush() |
Pageable | page(Page.of(7, 25)) |
Die verkürzten Abfragen sind HQL-Fragmente, keine eigene Sprache:
list("stelle", s) wird zu from Becher where stelle = ?1.
@ApplicationScoped
public class BecherRepo implements PanacheRepository<Becher> {
List<Becher> offen(String stelle) {
return list("stelle = ?1 and zurueck is null", Sort.by("ausgabe"), stelle);
}
}
quarkus.flyway.migrate-at-start=true
quarkus.hibernate-orm.database.generation=none # nicht beides zugleich
Modul: Persistence mit Hibernate ORM und Panache
Transaktionen und Querschnittsfunktionen
| Variante | Verhalten |
|---|---|
REQUIRED | startet eine Transaktion oder nutzt die vorhandene |
REQUIRES_NEW | setzt die laufende aus und startet eine neue |
MANDATORY | Fehler, wenn keine Transaktion läuft |
SUPPORTS | nutzt eine vorhandene, läuft sonst ohne |
NOT_SUPPORTED | setzt eine laufende Transaktion aus |
NEVER | Fehler, wenn eine Transaktion läuft |
Ungeprüfte Ausnahmen lösen ein Rollback aus; rollbackOn und dontRollbackOn
korrigieren das gezielt. Global gilt
quarkus.transaction-manager.default-transaction-timeout.
@Transactional
@TransactionConfiguration(timeout = 30)
public void abrechnen(String stelle) { ... }
QuarkusTransaction.requiringNew().run(() -> protokoll.schreiben(eintrag));
@CacheResult(cacheName = "pfandsaetze")
public BigDecimal satz(@CacheKey String becherTyp) { ... }
@Scheduled(cron = "{pfandkreis.abrechnung.cron}")
void abrechnungslauf() { ... }
Was in Spring über Aspekte gelöst war, übernehmen Interceptors. Der Caffeine-Cache ist prozesslokal, und der einfache Scheduler läuft in jeder Instanz — für Ausführung genau einmal im Cluster ist Quartz der vorgesehene Weg.
Modul: Transaktionen, Validierung und Querschnittsfunktionen
Typische Fallen
- Eine Build-Time-Property im Container gesetzt — sie bleibt wirkungslos, weil der Wert schon im Bytecode steht.
- Eine nur reflektiv genutzte Bean verschwindet, weil ArC sie als ungenutzt
entfernt;
@Unremovablehält sie. - Beans aus einer Fremdbibliothek ohne Jandex-Index bleiben unsichtbar.
- Bei Mehrdeutigkeit gibt es keinen Rückfall auf den Bean-Namen wie in Spring —
@Namedist Pflicht. - Eine Spring-Bibliothek kompiliert sauber und tut zur Laufzeit nichts, weil ihr Laufzeitmodell fehlt.
HttpServletRequestgibt es nur mitquarkus-resteasy, nicht mitquarkus-rest.- Das Fehlerformat ändert sich bei der Migration und bricht die Clients still — der eingebaute Validierungs-Mapper überschreibt gern das eigene Format.
@Value-Ausdrücke werden übernommen, obwohl@ConfigPropertykeine Expression Language kennt.application-prod.propertiessticht die Prozent-Einträge unbemerkt.- Eine Specification steckt tief in der Suchfunktion und fällt erst spät auf;
@Lockverschwindet dabei lautlos und mit ihm die Kollisionssicherheit. - Hibernate erzeugt das Schema, während Flyway migriert — beides zugleich aktiviert kollidiert.
- Eine geprüfte Ausnahme löst kein Rollback aus; ohne
rollbackOnbleibt es dabei. - Der nächtliche Lauf startet nach dem Skalieren dreimal gleichzeitig.