Vom Vertrag zum Code: Schnittstellen mit dem OpenAPI Generator erzeugen
https://bildung.social/@oerinformatik/
https://oer-informatik.de/spring-boot-openapi-codegenerierung
OpenAPI Generator, Codegenerierung, Contract First, Spring Boot, Bean Validation, Interface, Gradle, Maven, REST-API, Java
tl/dr; (ca. 19 min Lesezeit): Der OpenAPI Generator macht aus dem API-Vertrag Java-Schnittstellen und Datenklassen. Du bindest ihn in Gradle oder Maven ein, lässt den Controller für die Haltepunkte die erzeugte Schnittstelle implementieren und siehst, wie die Regeln aus dem Vertrag als Bean Validation fehlerhafte Anfragen mit
400abweisen. Weicht der Code vom Vertrag ab, merkt es der Compiler; ist der Vertrag fehlerhaft, bricht der Build ab [AI-Assisted]. (Zuletzt geändert am 11.11.2026)
Dieser Artikel ist Teil einer Artikelreihe über ein REST-Backend mit Spring Boot: Überblick und Fahrplan / 01 Anforderungen und API-Vertrag / 02 Projekt anlegen / 03 Codegenerierung / Übungsaufgaben zu 03
Schon fit in diesem Thema? Nutze die Kann-Liste und die Leitfragen unter dem Artikel als Checkliste, ob Du die relevanten Kompetenzen bereits hast.
Dieser Artikel baut auf der Kann-Liste der Artikel Vom Auftrag zur API und Das Projekt anlegen auf.
Als Spring-Boot-Grundlagen kann ich nach Durchlesen des Artikels…
… erklären, welches Problem die Codegenerierung aus einem API-Vertrag löst.
… den OpenAPI Generator in einen Gradle- oder Maven-Build einbinden und die wichtigsten Optionen erläutern.
… zuordnen, welche Angaben im Vertrag zu welchen Namen, Klassen und Annotationen im erzeugten Code werden.
… einen Controller notieren, der eine erzeugte Schnittstelle implementiert.
… erklären, warum eine Anfrage mit
400 Bad Requestoder501 Not Implementedbeantwortet wird.… erzeugten Code von eigenem Code unterscheiden und begründen, warum erzeugte Dateien nicht von Hand geändert werden.
Darüber hinaus kann ich als Spring-Boot-Expertenwissen…
… Fehlermeldungen von Build und Compiler auf Abweichungen zwischen Vertrag und Code zurückführen.
… begründen, welche Regeln Bean Validation aus dem Vertrag prüft und welche Fachregeln im Backend selbst geprüft werden müssen.
Im letzten Artikel hast Du den Controller für die Haltepunkte von Hand geschrieben und dabei Pfad, Methodennamen und Feldnamen aus dem Vertrag abgeschrieben. Bei zwei Endpunkten geht das gut. Bei elf Operationen, mehreren Personen im Team und einem Vertrag, der sich weiterentwickelt, laufen Vertrag und Code früher oder später auseinander. Dann antwortet das Backend mit fahrerID statt fahrerId, und ein Frontend, das sich auf den Vertrag verlässt, funktioniert nicht mehr.
Die Lösung: Der Code, der die Schnittstelle beschreibt, wird nicht geschrieben, sondern aus dem Vertrag erzeugt. Wir bleiben damit auf der untersten Stufe des V-Modells, der Implementierung.
Ausgangspunkt ist das Projekt aus Artikel 02. Das Ergebnis dieses Artikels findest Du im Ordner src/03-openapi-codegenerierung des Repositorys.
Der Vertrag kommt ins Projekt
Den Vertrag openapi.yaml aus Artikel 01 kopierst Du nach src/main/resources/openapi.yaml. Damit liegt er im selben Repository wie der Code, und jede Änderung am Vertrag ist in der Versionsverwaltung nachvollziehbar. Ab jetzt gilt: Wer die Schnittstelle ändern will, ändert zuerst den Vertrag.
Der OpenAPI Generator ist ein freies Werkzeug, das aus OpenAPI-Dateien Code für viele Sprachen und Frameworks erzeugt: Clients, Server, Dokumentation. Wir nutzen den Generator spring, der Schnittstellen und Datenklassen für Spring Boot schreibt. Er wird als Plugin in den Build eingebunden, sodass er bei jedem Build automatisch läuft.
Den Generator in den Build einbinden
Die Build-Datei aus Artikel 02 wächst um ein Plugin, eine Abhängigkeit und die Konfiguration des Generators:
Datei build.gradle.kts:
plugins {
java
id("org.springframework.boot") version "4.1.2"
id("io.spring.dependency-management") version "1.1.7"
id("org.openapi.generator") version "7.26.0"
}
group = "de.dbh"
version = "0.0.1-SNAPSHOT"
java {
toolchain {
languageVersion = JavaLanguageVersion.of(25)
}
}
repositories {
mavenCentral()
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-webmvc")
implementation("org.springframework.boot:spring-boot-starter-validation")
testImplementation("org.springframework.boot:spring-boot-starter-webmvc-test")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
// Schnittstellen und Datenklassen aus dem API-Vertrag erzeugen
openApiGenerate {
generatorName = "spring"
inputSpec = layout.projectDirectory.file("src/main/resources/openapi.yaml")
outputDir = layout.buildDirectory.dir("generated/openapi")
apiPackage = "de.dbh.mitfahrlabor.api"
modelPackage = "de.dbh.mitfahrlabor.api.model"
configOptions = mapOf(
"interfaceOnly" to "true",
"useTags" to "true",
"requestMappingMode" to "api_interface",
"configPackage" to "de.dbh.mitfahrlabor.api.config",
"useSpringBoot4" to "true",
"useJackson3" to "true",
"useBeanValidation" to "true",
"openApiNullable" to "false",
"documentationProvider" to "none",
"annotationLibrary" to "none",
"generateJsonIncludeAnnotations" to "false",
"generateJsonSetterNullsAnnotations" to "false",
)
}
// Vertrag prüfen
openApiValidate {
inputSpec = layout.projectDirectory.file("src/main/resources/openapi.yaml")
}
// erzeugten Code mitkompilieren, vorher erzeugen
sourceSets["main"].java.srcDir(layout.buildDirectory.dir("generated/openapi/src/main/java"))
tasks.compileJava { dependsOn(tasks.openApiGenerate) }
tasks.check { dependsOn(tasks.openApiValidate) }
tasks.withType<Test> {
useJUnitPlatform()
}Die Optionen unter configOptions steuern, was erzeugt wird:
| Option | Wert | Wirkung |
|---|---|---|
interfaceOnly |
true |
nur Schnittstellen und Datenklassen, keine fertigen Controller; die Logik schreiben wir selbst |
useTags |
true |
eine Schnittstelle je tag des Vertrags: HaltepunkteApi, AngeboteApi, BuchungenApi, MonitorApi |
requestMappingMode |
api_interface |
den gemeinsamen Pfad /v1 aus servers als @RequestMapping an die Schnittstelle schreiben |
configPackage |
de.dbh.mitfahrlabor.api.config |
Hilfsklassen in ein Paket legen, das der Komponentenscan findet (siehe unten) |
useSpringBoot4, useJackson3 |
true |
Code für Spring Boot 4 und die JSON-Bibliothek Jackson 3 |
useBeanValidation |
true |
Regeln wie required, minimum oder pattern als Annotationen erzeugen |
openApiNullable, documentationProvider, annotationLibrary |
false, none, none |
keine zusätzlichen Bibliotheken für optionale Werte und Dokumentation |
generateJsonIncludeAnnotations, generateJsonSetterNullsAnnotations |
false |
bisheriges Verhalten beim Schreiben und Lesen von null beibehalten (ohne Angabe gibt der Generator Warnungen aus) |
Die Abhängigkeit spring-boot-starter-validation ist nötig, damit die erzeugten Prüf-Annotationen zur Laufzeit auch ausgewertet werden.
Die Option configPackage verdient einen zweiten Blick. Der Generator erzeugt eine Hilfsklasse, die Werte wie zur-schule aus der URL in die Java-Aufzählung Richtung umwandelt. Ohne die Option landet sie im Paket org.openapitools.configuration, außerhalb von de.dbh.mitfahrlabor. Der Komponentenscan findet sie dann nicht, und eine Anfrage wie GET /v1/angebote?richtung=zur-schule scheitert mit 400 Bad Request. Das ist dieselbe Falle wie bei einem Controller im falschen Paket.
Was der Generator erzeugt
Ein Build erzeugt jetzt vor dem Kompilieren einen eigenen Quellcode-Ordner:
Bei Gradle (./gradlew build) liegt er unter build/generated/openapi:
build/generated/openapi/src/main/java/de/dbh/mitfahrlabor/
└── api/
├── AngeboteApi.java Schnittstelle je tag
├── BuchungenApi.java
├── HaltepunkteApi.java
├── MonitorApi.java
├── ApiUtil.java Hilfsklasse für Beispielantworten
├── config/
│ └── EnumConverterConfiguration.java
└── model/
├── Angebot.java Klasse je Schema
├── AngebotAenderung.java
├── AngebotNeu.java
├── AngebotStatus.java enum
├── Buchung.java
├── … (insgesamt 12 Klassen und Aufzählungen)
└── Richtung.java enum
Der Ablauf im Build sieht so aus: Der Generator liest den Vertrag und schreibt Java-Dateien in den Build-Ordner. Der Compiler übersetzt sie zusammen mit Deinen eigenen Klassen. Erst dann laufen die Tests und entsteht die JAR-Datei.
Der erzeugte Code liegt bewusst im Build-Ordner und nicht unter src: Er wird bei jedem Build neu geschrieben und gehört nicht in die Versionsverwaltung. Wer eine erzeugte Datei von Hand ändert, verliert die Änderung beim nächsten Build. Die erste Zeile jeder Datei warnt deshalb: „Do not edit the class manually.“
Die Schnittstelle HaltepunkteApi sieht gekürzt so aus:
@Validated
@RequestMapping("${openapi.mitfahrlabor.base-path:/v1}")
public interface HaltepunkteApi {
String PATH_LISTE_HALTEPUNKTE = "/haltepunkte";
@RequestMapping(
method = RequestMethod.GET,
value = HaltepunkteApi.PATH_LISTE_HALTEPUNKTE,
produces = { "application/json" }
)
default ResponseEntity<List<Haltepunkt>> listeHaltepunkte() {
// … Beispielantwort aus dem Vertrag vorbereiten …
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
}
String PATH_HOLE_HALTEPUNKT = "/haltepunkte/{haltepunktId}";
@RequestMapping(
method = RequestMethod.GET,
value = HaltepunkteApi.PATH_HOLE_HALTEPUNKT,
produces = { "application/json", "application/problem+json" }
)
default ResponseEntity<Haltepunkt> holeHaltepunkt(
@PathVariable("haltepunktId") Long haltepunktId
) {
// …
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
}
}Hier stehen alle Annotationen, die Du in Artikel 02 selbst geschrieben hast: Pfad, HTTP-Methode, Pfadvariable. Neu ist zweierlei. Erstens sind die Methoden default-Methoden, also Methoden einer Schnittstelle mit Rumpf. Wer die Schnittstelle implementiert, muss sie nicht überschreiben; dann antwortet der Endpunkt mit 501 Not Implemented. Zweitens steht der Pfad /v1 in einem Platzhalter, der sich über die Eigenschaft openapi.mitfahrlabor.base-path in application.properties ändern ließe.
Aus jedem Schema unter components/schemas wird eine Klasse im Paket api.model, aus den Aufzählungen eine Java-enum. Die Regeln aus dem Vertrag stehen als Bean-Validation-Annotationen an den Gettern. Für das Feld freiePlaetze von AngebotNeu sieht das so aus:
/**
* Get freiePlaetze
* minimum: 1
* maximum: 8
* @return freiePlaetze
*/
@NotNull @Min(value = 1) @Max(value = 8)
@JsonProperty("freiePlaetze")
public Integer getFreiePlaetze() {
return freiePlaetze;
}Die Zuordnung folgt festen Regeln:
| im Vertrag | im erzeugten Java-Code |
|---|---|
tags: [haltepunkte] |
Schnittstelle HaltepunkteApi |
operationId: holeHaltepunkt |
Methode holeHaltepunkt(…) |
Pfadparameter haltepunktId |
Parameter @PathVariable("haltepunktId") Long haltepunktId |
Schema AngebotNeu |
Klasse AngebotNeu mit Gettern, Settern und Konstruktor |
required: [datum, …] |
@NotNull am Getter |
minimum: 1, maximum: 8 |
@Min(value = 1), @Max(value = 8) |
pattern: '^([01][0-9]…' |
@Pattern(regexp = "^([01][0-9]…") |
enum: [zur-schule, nach-hause] |
enum Richtung { ZUR_SCHULE("zur-schule"), NACH_HAUSE("nach-hause") } |
type: integer, format: int64 |
Long |
type: string, format: date |
LocalDate |
Anders als in Artikel 02 sind die Datenklassen keine Records, sondern klassische Klassen mit Gettern und Settern. Zusätzlich gibt es für jedes Feld eine Methode, die das Objekt selbst zurückgibt, zum Beispiel angebotNeu.freiePlaetze(2). Damit lassen sich Objekte in einer Kette aufbauen: new Haltepunkt().id(1L).name("Marktplatz").ort("Halle (Westf.)").
Der Controller implementiert die Schnittstelle
Der Controller aus Artikel 02 wird deutlich kürzer. Der Record HaltepunktDto fällt weg, an seine Stelle tritt die erzeugte Klasse Haltepunkt:
package de.dbh.mitfahrlabor.web;
import java.util.List;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;
import de.dbh.mitfahrlabor.api.HaltepunkteApi;
import de.dbh.mitfahrlabor.api.model.Haltepunkt;
@RestController
public class HaltepunktController implements HaltepunkteApi {
private final List<Haltepunkt> haltepunkte = List.of(
new Haltepunkt(1L, "Hauptbahnhof, ZOB Steig 3", "Gütersloh", 18.6),
new Haltepunkt(2L, "Park & Ride am Bahnhof", "Herford", 17.9),
new Haltepunkt(3L, "Marktplatz", "Halle (Westf.)", 15.2),
new Haltepunkt(4L, "Busbahnhof", "Bad Salzuflen", 19.4),
new Haltepunkt(5L, "Kirchplatz", "Werther (Westf.)", 13.1),
new Haltepunkt(6L, "Bahnhof Brackwede", "Bielefeld", 6.2));
@Override
public ResponseEntity<List<Haltepunkt>> listeHaltepunkte() {
return ResponseEntity.ok(haltepunkte);
}
@Override
public ResponseEntity<Haltepunkt> holeHaltepunkt(Long haltepunktId) {
return haltepunkte.stream()
.filter(h -> h.getId().equals(haltepunktId))
.findFirst()
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
}Am Controller steht nur noch @RestController; Pfade, Methoden und Parameter erbt er von der Schnittstelle. Die Annotation @Override ist hier mehr als Schmuck: Sie verlangt, dass es die Methode in der Schnittstelle gibt. Benennt jemand im Vertrag die operationId um, heißt die Methode in der Schnittstelle anders, und der Compiler meldet:
HaltepunktController.java:32: error: method does not override or implement a method from a supertype
Genau das wollen wir: Weicht der Code vom Vertrag ab, fällt es beim Bauen auf und nicht erst beim Frontend. Weil die Haltepunkte jetzt als Long statt long übergeben werden, vergleicht der Controller die IDs mit equals() statt mit ==.
Das Klassendiagramm zeigt die Arbeitsteilung: Im Paket api liegt, was der Generator schreibt, im Paket web, was Du schreibst.
Regeln aus dem Vertrag: Bean Validation
Um die Prüfungen auszuprobieren, braucht es einen Endpunkt, der Daten annimmt. Ein vorläufiger AngebotController implementiert von AngeboteApi nur das Anlegen und Abrufen und speichert die Angebote in einer Liste im Arbeitsspeicher. Die Fachregeln wie der Kostenbeitrag je Kilometer fehlen noch; sie folgen in den Artikeln 04 und 08.
@RestController
public class AngebotController implements AngeboteApi {
private final List<Angebot> angebote = new ArrayList<>();
private final AtomicLong naechsteId = new AtomicLong(1);
@Override
public ResponseEntity<Angebot> legeAngebotAn(AngebotNeu neu) {
Angebot angebot = new Angebot(neu.getFahrerId(), neu.getHaltepunktId(), neu.getRichtung(),
neu.getDatum(), neu.getAbfahrt(), neu.getFreiePlaetze(), neu.getKostenbeitragEur(),
naechsteId.getAndIncrement(), AngebotStatus.OFFEN);
angebote.add(angebot);
return ResponseEntity.created(URI.create("/v1/angebote/" + angebot.getId())).body(angebot);
}
@Override
public ResponseEntity<Angebot> holeAngebot(Long angebotId) {
return angebote.stream()
.filter(a -> a.getId().equals(angebotId))
.findFirst()
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
}ResponseEntity.created(…) antwortet mit 201 Created und setzt den Header Location auf die Adresse des neuen Angebots, so wie es der Vertrag verlangt. Die Datei angebote.http im Projekt enthält passende Anfragen. Ein gültiges Angebot wird angelegt:
POST http://localhost:8080/v1/angebote
Content-Type: application/json
{"fahrerId": 1, "haltepunktId": 1, "richtung": "zur-schule", "datum": "2026-11-19",
"abfahrt": "07:00", "freiePlaetze": 2, "kostenbeitragEur": 1.80}
HTTP/1.1 201
Location: /v1/angebote/1
Content-Type: application/json
{"fahrerId":1,"haltepunktId":1,"richtung":"zur-schule","datum":"2026-11-19","abfahrt":"07:00",
"freiePlaetze":2,"kostenbeitragEur":1.8,"id":1,"status":"offen"}
Fehlt dagegen das Datum, steht als Uhrzeit "7 Uhr" und sind es 12 Plätze, antwortet das Backend mit 400 Bad Request, ohne dass Deine Methode legeAngebotAn() überhaupt aufgerufen wird. Der Parameter ist in der Schnittstelle mit @Valid gekennzeichnet; Spring prüft deshalb alle Annotationen des Objekts, bevor es die Methode aufruft. Im Log steht, welche Regeln verletzt sind:
MethodArgumentNotValidException: Validation failed for argument [0] … with 3 errors:
Field error in object 'angebotNeu' on field 'freiePlaetze': rejected value [12] … Max
Field error in object 'angebotNeu' on field 'abfahrt': rejected value [7 Uhr] … Pattern
Field error in object 'angebotNeu' on field 'datum': rejected value [null] … NotNull
Auch eine unbekannte Richtung wie "zum-bahnhof" führt zu 400, weil sich der Wert nicht in die Aufzählung Richtung umwandeln lässt. Die Antwort enthält im Moment nur eine allgemeine Fehlermeldung von Spring. In Artikel 09 wird daraus eine Antwort im Format Problem Details, die dem Frontend genau sagt, welches Feld falsch ist.
Was Bean Validation nicht prüfen kann, zeigt dieselbe Anfrage mit einem Kostenbeitrag von 5,00 Euro: Sie wird angelegt. Die Grenze von 0,10 Euro je Kilometer hängt von der Entfernung des Haltepunkts ab, und die steht nicht in der Anfrage. Dafür braucht es Fachlogik, und die schreiben wir im nächsten Artikel.
Was antwortet ein Endpunkt, den es noch nicht gibt?
Der Vertrag beschreibt elf Operationen, umgesetzt sind vier. Die anderen verhalten sich unterschiedlich:
GET /v1/angeboteantwortet mit501 Not Implemented. Es gibt einen Controller, derAngeboteApiimplementiert, aber die MethodesucheAngebote()nicht überschreibt. Also läuft diedefault-Methode der Schnittstelle.GET /v1/buchungen/1antwortet mit404 Not Found. Die SchnittstelleBuchungenApiist zwar erzeugt, aber keine Klasse mit@RestControllerimplementiert sie. Eine Schnittstelle allein ist keine Bean, und Spring kennt den Pfad deshalb nicht.
Das ist praktisch für die weitere Entwicklung: Ein Frontend-Team kann schon gegen die Pfade arbeiten und sieht an 501, welche Teile noch fehlen.
Den Vertrag im Build prüfen
Der Generator prüft den Vertrag, bevor er Code erzeugt. Ist der Vertrag fehlerhaft, bricht der Build ab. Zusätzlich gibt es eine eigene Aufgabe openApiValidate, die in Gradle mit check mitläuft und später in der Pipeline (Artikel 05) eine eigene Stufe bekommt:
# nur den Vertrag prüfen
.\gradlew.bat openApiValidate
# Code erzeugen, kompilieren, testen, Vertrag prüfen, JAR bauen
.\gradlew.bat buildEin Tippfehler im Vertrag, etwa type: integr statt type: integer und parameter: statt parameters:, liefert:
> Task :openApiValidate FAILED
Spec has issues or recommendations.
Issues:
The 'type' attribute must be one of 'null', 'boolean', 'object', 'array', 'number', 'string', or 'integer'.
Spec is invalid.
Issues:
attribute paths.'/haltepunkte/{haltepunktId}'(get).parameter is unexpected
Damit gibt es zwei Sicherungen: Der Generator stellt sicher, dass der Vertrag gültig ist, und der Compiler stellt sicher, dass der Code zum Vertrag passt. Ob die Antworten inhaltlich stimmen, prüfen erst die Tests ab Artikel 04. Der Smoke-Test aus Artikel 02 läuft weiterhin mit und bestätigt, dass die Anwendung mit den erzeugten Klassen startet.
Nächster Artikel
Der Vertrag ist jetzt im Code verankert. Was fehlt, sind die Regeln, die der Vertrag nicht ausdrücken kann: der Kostenbeitrag je Kilometer, die Einwilligung bei Minderjährigen, die freien Plätze. Im nächsten Artikel schreibst Du diese Fachlogik in reinem Java, ganz ohne Spring, und prüfst sie mit den ersten richtigen Tests: Komponententests mit JUnit.
Leitfragen
- Welches Problem löst die Codegenerierung aus dem API-Vertrag?
Sie verhindert, dass Vertrag und Code auseinanderlaufen: Pfade, Methoden, Parameter und Feldnamen werden aus dem Vertrag erzeugt statt abgeschrieben, und Abweichungen fallen beim Kompilieren auf. siehe Abschnitt „Der Controller implementiert die Schnittstelle“
- Was bewirken die Optionen
interfaceOnlyunduseTags?
interfaceOnly erzeugt nur Schnittstellen und Datenklassen, keine Controller mit Logik. useTags erzeugt eine Schnittstelle je tag des Vertrags, etwa HaltepunkteApi und AngeboteApi. siehe Abschnitt „Den Generator in den Build einbinden“
- Woraus entsteht der Name der Methode
holeHaltepunkt()und woraus die Annotation@Max(value = 8)?
Der Methodenname kommt aus der operationId der Operation, die Annotation @Max(value = 8) aus maximum: 8 im Schema des Feldes. siehe Abschnitt „Was der Generator erzeugt“
- Warum werden die erzeugten Dateien nicht von Hand geändert und nicht in die Versionsverwaltung aufgenommen?
Sie werden bei jedem Build aus dem Vertrag neu geschrieben; Änderungen von Hand gingen verloren. Versioniert wird der Vertrag, aus dem sie entstehen. siehe Abschnitt „Was der Generator erzeugt“
- Warum antwortet
GET /v1/angebotemit501,GET /v1/buchungen/1aber mit404?
AngeboteApi wird vom AngebotController implementiert, die nicht überschriebene default-Methode antwortet mit 501. BuchungenApi implementiert keine Klasse; ohne Bean kennt Spring den Pfad nicht und antwortet mit 404. siehe Abschnitt „Was antwortet ein Endpunkt, den es noch nicht gibt?“
- Welche Regeln aus dem Mitfahrlabor prüft Bean Validation, welche nicht?
Bean Validation prüft, was man der Anfrage allein ansieht: Pflichtfelder, Zahlenbereiche, Muster und erlaubte Werte. Regeln, die gespeicherte Daten brauchen, wie den Kostenbeitrag je Kilometer, prüft sie nicht; dafür ist die Fachlogik zuständig. siehe Abschnitt „Regeln aus dem Vertrag: Bean Validation“
- Warum muss die Option
configPackageauf ein Unterpaket vonde.dbh.mitfahrlaborzeigen?
Die erzeugte Hilfsklasse zum Umwandeln von Aufzählungswerten in der URL muss eine Bean werden. Der Komponentenscan durchsucht nur das Paket der Startklasse und dessen Unterpakete; liegt die Klasse woanders, scheitern Anfragen wie ?richtung=zur-schule mit 400. siehe Abschnitt „Den Generator in den Build einbinden“
Links und weitere Informationen
zum Weiterlernen: Übungsaufgaben zur Codegenerierung
als Nachschlagewerk: OpenAPI Generator: Dokumentation des Generators „spring“
als Nachschlagewerk: OpenAPI Generator: Gradle-Plugin und Maven-Plugin
als Nachschlagewerk: Jakarta Bean Validation
als Hintergrund: Build-Tool Gradle
Hinweis zur Nachnutzung als Open Educational Resource (OER)
Dieser Artikel und seine Texte, Bilder, Grafiken, Code und sonstiger Inhalt sind - sofern nicht anders angegeben - lizenziert unter CC BY 4.0. Nennung gemäß TULLU-Regel bitte wie folgt: “Vom Vertrag zum Code: Schnittstellen mit dem OpenAPI Generator erzeugen” von oer-informatik.de (H. Stein), Lizenz: CC BY 4.0. Der Artikel wurde unter https://oer-informatik.de/spring-boot-openapi-codegenerierung veröffentlicht, die Quelltexte sind in weiterverarbeitbarer Form verfügbar im Repository unter https://gitlab.com/oer-informatik/java-springboot/Backend. Erstellt mit KI-Assistenz; von mir umfassend redaktionell überarbeitet, geprüft und freigegeben. Stand: 11.11.2026.
[Kommentare zum Artikel lesen, schreiben] / [Artikel teilen] / [gitlab-Issue zum Artikel schreiben]
