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.
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 →