Start / Seminare / Spring Boot 4 Grundlagen

Modul

RESTful APIs mit Spring Boot

8 Kapitel in diesem Modul-Video · Laufzeit

Vertonung mit synthetischer Stimme · Inhalte redaktionell verantwortet · © 2026 HECKER CONSULTING, alle Rechte vorbehalten

Transkript

Der gesprochene Text dieses Moduls zum Mitlesen, Überfliegen und Durchsuchen. Ein Klick auf einen Zeitstempel springt an die Stelle im Video.

RESTful APIs mit Spring Boot

0:00 Das ist das umfangreichste Modul des Seminars, und das aus gutem Grund: REST-Dienste sind das, wofür Spring Boot am häufigsten eingesetzt wird. Wir bauen Radwerk in diesem Modul von einer leeren Anwendung zu einem Dienst aus, den man tatsächlich befragen kann. Dabei geht es nicht nur um Annotationen. Es geht um Entwurfsfragen, die man leicht unterschätzt — welcher Parameter gehört in den Pfad, welcher in die Abfrage, wo behandelt man Fehler, wann prüft man Eingaben.

0:27 Diese Entscheidungen prägen eine Schnittstelle dauerhaft, denn sie zurückzunehmen kostet später alle Aufrufer Arbeit.

RESTful APIs mit Spring Boot

0:34 Wir gehen den Weg einer einzelnen Anfrage von außen nach innen. Zuerst das Framework darunter, dann das Datenmodell und der Zugriff darauf, dann die Controller, die Anfragen entgegennehmen. Danach probieren wir den Dienst aus und stoßen prompt auf einen Fehler, der uns zum Thema Fehlerbehandlung führt. Zum Schluss zwei Themen, die im Betrieb wichtig werden: Eingaben prüfen und die Schnittstelle versionieren. Acht Kapitel, aber ein durchgehender Faden.

Spring Web MVC verstehen

1:01 Bevor wir Code schreiben, kurz das Fundament. Spring Web MVC ist der Teil, der Anfragen entgegennimmt und Antworten erzeugt. Der Name klingt nach einem Konzept aus den Neunzigern — was er auch ist. Die Frage ist, was davon in einem REST-Dienst noch trägt. Model, View, Controller ist eine der ältesten Aufteilungen in der Softwarearchitektur, und sie hält sich, weil sie eine sinnvolle Frage beantwortet: Wer ist wofür zuständig?

1:28 Das Model sind die Daten, mit denen gearbeitet wird. Die View übersetzt sie in das, was der Aufrufer sieht. Und der Controller nimmt Eingaben entgegen, stößt Logik an, erzeugt Ausgabe. In einer klassischen Webanwendung erzeugt die View HTML. In einem REST-Dienst ist sie unauffälliger — sie formatiert die Daten als JSON. Das passiert bei uns automatisch, weshalb man leicht vergisst, dass es diese dritte Rolle überhaupt gibt.

1:55 Um den Wert dieser Liste zu erkennen, hilft ein Blick zurück. Die Servlet-API gibt es fast so lange wie Java selbst, und sie ist bemerkenswert spartanisch: Sie können eine HTTP-Anfrage entgegennehmen und eine Antwort schreiben. Alles andere ist Ihre Sache — den Pfad auswerten, Parameter herausziehen, Typen umwandeln, Fehler abfangen, JSON erzeugen.

2:15 Jede dieser vier Zeilen auf der Folie steht für Code, den früher jedes Projekt selbst geschrieben hat, und zwar jedes Mal ein bisschen anders. Dass das heute wegfällt, ist der eigentliche Fortschritt — nicht die Annotationen an sich. Der DispatcherServlet ist die Stelle, an der die alte und die neue Welt aufeinandertreffen. Nach außen ist er ein ganz gewöhnliches Servlet, wie die Servlet-API es kennt.

2:39 Nach innen weiß er, welche Controller es gibt und welche Methode zu welcher Anfrage passt. Man kann ihn sich wie die Poststelle eines großen Hauses vorstellen: Alles kommt dort an, und von dort wird es an die richtige Abteilung verteilt. Praktisch werden Sie ihm selten begegnen. Die Autokonfiguration richtet ihn beim Start ein — wieder ein Fall, in dem Sie etwas benutzen, ohne es angefordert zu haben.

3:02 Diese Tabelle ist im Grunde eine Empfehlung mit Begründung. WebFlux ist technisch das modernere Modell — nicht blockierend, damit sparsamer mit Ressourcen bei sehr vielen gleichzeitigen Verbindungen. Der Preis steht in den mittleren Zeilen, und er ist hoch: ein anderer Programmierstil, eine steile Einarbeitung und, besonders unangenehm, eine deutlich mühsamere Fehlersuche.

3:24 Reaktive Ströme zu debuggen ist eine eigene Fertigkeit. Deshalb die klare Empfehlung in der letzten Zeile: Fangen Sie mit Web MVC an. Es ist ausgereift und für die allermeisten Anwendungen völlig ausreichend. Wechseln Sie erst, wenn Sie den Nutzen belegen können. Der erste Punkt begegnet mir häufiger, als mir lieb ist: WebFlux wird gewählt, weil es moderner klingt, und ein halbes Jahr später sucht das Team Fehler in Aufrufketten, die niemand mehr überblickt.

3:52 Der zweite ist harmlos — man versucht, den DispatcherServlet selbst zu konfigurieren, obwohl das nicht nötig ist. Der dritte ist der, der Sie im Alltag am ehesten trifft: fachliche Logik in den Controller zu schreiben. Das geht anfangs gut und wird dann zum Problem, wenn dieselbe Logik von woanders gebraucht wird. Ein Controller nimmt entgegen, gibt weiter und antwortet. Mehr nicht.

Das Datenmodell von Radwerk

4:14 Bevor Controller etwas zurückgeben können, brauchen wir etwas, das sie zurückgeben. Wir legen also zuerst das Datenmodell an — fünf Records, mehr braucht der ganze Dienst nicht. Radwerk soll zwei Dinge können, und diese beiden Aufgaben bestimmen das Modell. Erstens Auskunft über das Serviceangebot geben, zweitens Anmeldungen entgegennehmen.

4:35 Daraus ergibt sich eine Kette: Eine Filiale betreibt Servicetermine an einem Standort, zu jedem Termin gibt es buchbare Leistungen, und wer bucht, erzeugt eine Anmeldung. Diese Kette sollten Sie im Kopf behalten, denn sie taucht gleich in den Pfaden der Schnittstelle wieder auf. Wenn Sie später überlegen, warum ein Endpunkt so heißt, wie er heißt: Meist ist es diese Kette, die sich darin abbildet.

4:59 Statt die Felder vorzulesen, achten Sie auf ein Muster. Jeder Record hat eine Kennung, und mehrere haben zusätzlich eine Kennung, die auf einen anderen Record zeigt — die Leistung verweist auf den Termin, die Anmeldung auf die Leistung. Beim Servicetermin dagegen stehen Filiale und Standort als ganze Objekte. Das ist eine bewusste Entscheidung und keine Inkonsequenz: Wo wir das verknüpfte Objekt ohnehin mit ausliefern wollen, halten wir es als Objekt; wo es nur um die Zuordnung geht, genügt die Kennung.

5:28 In Modul vier werden aus genau diesen Beziehungen dann Datenbankverknüpfungen. Warum Records und nicht gewöhnliche Klassen? Weil diese Objekte genau eine Aufgabe haben: Daten tragen. Sie brauchen keine eigene Logik, sie sollen sich nicht verändern, und sie sollen kurz zu schreiben sein. Genau dafür wurden Records gemacht — eine Zeile statt Feldern, Konstruktor, Zugriffsmethoden und den üblichen Vergleichsmethoden.

5:53 Merken Sie sich diese Entscheidung aber gut, denn wir nehmen sie in Modul vier teilweise zurück. Für die Datenbankabbildung mit JPA sind Records nämlich ungeeignet, und der Grund dafür ist ausgerechnet ihre schönste Eigenschaft: die Unveränderlichkeit. Ein durchgespieltes Beispiel macht das Modell greifbarer als jede Feldliste. Die Filiale Radwerk Nord veranstaltet in der Speichergasse eine Frühjahrscheck-Woche.

6:18 Das ist der Servicetermin. Dazu gibt es zwei Leistungen — eine kleine und eine große Inspektion. Wer sich anmeldet, wählt eine davon und erhält eine Anmeldung mit einer Auftragsnummer, die in der Werkstatt vorgelegt wird. Diese Auftragsnummer ist übrigens ein gutes Beispiel für ein Feld, das nicht von außen kommt, sondern beim Anlegen erzeugt wird.

6:38 Das wird gleich wichtig, wenn wir überlegen, was ein Aufrufer überhaupt schicken darf. Der erste Punkt ist die häufigste Krankheit von Datenmodellen: Felder, die jemand für später eingebaut hat. Sie kosten in jeder Schicht Aufmerksamkeit, und niemand traut sich, sie zu entfernen. Der zweite ist subtiler — eine Beziehung doppelt abzubilden, einmal als Objekt und einmal als Kennung. Das führt verlässlich dazu, dass beide auseinanderlaufen.

7:05 Und der dritte ist die Versuchung, Records mit Logik zu füllen. Ein bisschen Berechnung hier, eine Prüfung dort, und plötzlich ist der Datenträger ein Fachobjekt, das an drei Stellen im System eine Meinung hat.

Daten über Repositories

7:18 Jetzt brauchen die Records noch einen Ort, an dem sie herkommen. Wir bauen dafür Repositories — vorerst allerdings mit einer bewusst primitiven Umsetzung. Warum das trotzdem sinnvoll ist, sehen wir gleich. Ein Repository kapselt den Zugriff auf einen Datenbestand: anlegen, lesen, ändern, löschen. Wer den Begriff Data Access Object kennt — es ist dieselbe Idee.

7:41 In diesem Modul geht es um Controller, deshalb halten wir die Speicherung bewusst dumm: eine fest verdrahtete Liste mit Beispieldaten. Das ist keine Faulheit, sondern Methode. Denn genau diese Trennung erlaubt es uns, in Modul vier eine echte Datenbank dahinterzusetzen, ohne dass die Controller sich ändern. Wenn Sie sich fragen, wozu die Abstraktion gut ist: Genau dafür. Sie werden es in zwei Modulen selbst erleben.

8:07 Auf ein Detail möchte ich hier besonders hinweisen, und es steht in der Fußzeile: findById liefert kein Objekt, sondern ein Optional. Das ist eine Entwurfsentscheidung mit weitreichenden Folgen. Ein Optional zwingt den Aufrufer, sich zu der Frage zu verhalten, ob überhaupt etwas gefunden wurde. Genau diese Frage entscheidet später über den Unterschied zwischen Status zweihundert und Status vierhundertvier.

8:29 Wir werden im Kapitel zur Fehlerbehandlung sehen, was passiert, wenn man sich vor dieser Frage drückt — nämlich einen Serverfehler, wo eine schlichte Auskunft angemessen gewesen wäre. Technisch gesehen könnten Sie hier ebenso gut Component schreiben — Repository ist selbst damit annotiert und wirkt über den Meta-Mechanismus genauso.

8:49 Warum also die speziellere Variante? Wegen der beiden unteren Punkte. Der eine ist menschlich: Der Name sagt der nächsten Leserin, welche Rolle diese Klasse spielt, ohne dass sie den Code lesen muss. Der andere ist technisch: Spring kann datenzugriffsspezifische Ausnahmen einheitlich übersetzen. Das ist heute unauffällig, wird aber in Modul vier relevant, wenn eine echte Datenbank dahintersteht und Treiber ihre eigenen Ausnahmen werfen.

9:15 Der erste Punkt ist die Gefahr jeder Zwischenlösung: Sie hält. Eine fest verdrahtete Liste funktioniert erstaunlich lange, und irgendwann ist sie in Produktion. Halten Sie fest, dass sie eine Etappe ist. Der zweite Punkt ist der wichtigste: findById einen Wert statt eines Optional zurückgeben zu lassen. Damit verlagern Sie das Problem des Nichtvorhandenseins nach hinten, wo es dann als Nullwert auftaucht.

9:39 Und der dritte betrifft den Schnitt: Repositories gehören zu einem Datenbestand, nicht zu einem Aufrufer. Sobald Sie ein Repository für einen bestimmten Controller bauen, stimmt etwas nicht.

REST-Controller umsetzen

9:51 Jetzt kommt das Herzstück des Moduls. Wir machen aus einer gewöhnlichen Java-Klasse etwas, das HTTP-Anfragen entgegennimmt — und zwar mit erstaunlich wenig Zutun. Der TerminController ist zunächst eine ganz normale Klasse. Er bekommt die benötigten Repositories über den Konstruktor — das ist gewöhnliche Injektion, nichts Besonderes — und hat vier Methoden: Filialen auflisten, Termine einer Filiale suchen, einen Termin nach Kennung holen, die Leistungen eines Termins liefern.

10:20 Wenn Sie sich diese vier Methoden ansehen, fällt auf, dass keine davon nennenswerte Logik enthält. Alle geben die Anfrage an ein Repository weiter. Das ist kein Zeichen dafür, dass wir es uns leicht machen — das ist genau die Aufgabe eines Controllers. RestController ist wieder eine dieser zusammengesetzten Annotationen, und die Zerlegung lohnt sich.

10:41 Der eine Bestandteil macht die Klasse über die Komponentensuche auffindbar — sie wird also ein Bean wie jedes andere. Der zweite ist der eigentlich interessante: Er sorgt dafür, dass der Rückgabewert jeder Methode direkt in den Rumpf der Antwort geschrieben wird. Ohne diesen Teil würde Spring den Rückgabewert als Namen einer View interpretieren und nach einer HTML-Vorlage suchen.

11:01 Das ist übrigens der häufigste Grund für leere Antworten bei Leuten, die aus Versehen die schlichte Controller-Annotation verwendet haben. Worauf es hier ankommt, sind die zwei Zeilen mit den Pfaden. Beim ersten Endpunkt steht der Pfad allein, und die Filialkennung kommt aus einem Abfrageparameter. Beim zweiten steht ein Platzhalter in geschweiften Klammern im Pfad, und der Parameter wird daran gebunden.

11:25 Beide Male passiert dasselbe: Spring Web MVC zieht einen Wert aus der Anfrage und übergibt ihn typgerecht an Ihre Methode. Die Umwandlung von Text nach Zahl macht das Framework. Was Sie entscheiden müssen, ist allein die Frage, woher der Wert kommen soll — und genau diese Frage behandeln wir gleich ausführlich. Diese drei Annotationen decken praktisch alles ab, was Sie brauchen werden.

11:48 Das Prinzip dahinter ist einfach: Eine HTTP-Anfrage besteht aus mehreren Teilen — Pfad, Abfrageparameter, Kopfzeilen, Rumpf — und für jeden dieser Teile gibt es eine Annotation, die daraus einen Methodenparameter macht. Der Rumpf spielt erst eine Rolle, wenn wir Daten entgegennehmen, also beim Anlegen einer Anmeldung. Wichtig ist, dass diese Bindung deklarativ ist: Sie beschreiben, was Sie wollen, und lesen nicht selbst aus einem Anfrageobjekt.

12:15 Das ist der Unterschied zur nackten Servlet-API von vorhin. Der erste Punkt ist der eben erwähnte Klassiker mit den leeren Antwortrümpfen — und er ist deshalb so tückisch, weil die Anwendung startet und der Endpunkt antwortet. Nur eben mit nichts. Der zweite ist ein reiner Flüchtigkeitsfehler: Ein Platzhalter im Pfad, aber kein passend gebundener Parameter.

12:36 Der dritte ist eine Entwurfsschwäche, die man häufig sieht: Alle Parameter über den Pfad zu führen, auch reine Suchkriterien. Das ergibt Pfade, die aussehen wie Datenbankabfragen, und Ressourcen, die keine sind. Warum das ein Problem ist, klärt das nächste Kapitel.

Den Dienst benutzen

12:53 Genug gebaut — jetzt fragen wir den Dienst tatsächlich. Und daran hängt die interessanteste Entwurfsfrage dieses Moduls, die man leicht übergeht, weil beide Varianten technisch funktionieren. Drei Aufrufe, drei Antworten im JSON-Format. Der eigentliche Punkt ist, was Sie hier nicht sehen: Niemand hat JSON geschrieben. Ihre Methoden geben Java-Objekte zurück, und die Umwandlung übernimmt Spring Web MVC mit der Bibliothek, die Spring Boot dafür ausgewählt hat.

13:20 Achten Sie beim Ausprobieren nicht nur auf den Inhalt der Antwort, sondern auch auf den Statuscode. Der ist bei erfolgreichen Anfragen zweihundert und wird uns im nächsten Kapitel beschäftigen, wenn wir nach etwas fragen, das es nicht gibt. Dann nämlich stimmt er zunächst nicht. Hier steht die Entwurfsregel, um die es mir geht. REST dreht sich um Ressourcen, und ein Pfad benennt eine Ressource.

13:43 Wenn Sie einen Termin über seinen Schlüssel holen, dann benennen Sie damit genau ein Ding — das gehört in den Pfad. Wenn Sie dagegen nach Kriterien suchen, etwa nach allen Terminen einer Filiale, dann benennen Sie kein einzelnes Ding, sondern filtern eine Menge. Das gehört in Abfrageparameter. Der letzte Punkt ist ernst gemeint: Diese Entscheidung prägt die Schnittstelle dauerhaft. Sie zurückzunehmen bedeutet, dass alle Aufrufer ihren Code ändern müssen.

14:10 Das Erfolgskriterium hat zwei Teile, und der zweite ist der eigentliche. Die vier Endpunkte aufzurufen ist schnell erledigt. Interessanter wird es bei der Begründung: Sagen Sie für jeden Parameter, warum er im Pfad oder in der Abfrage steht. Wenn Sie bei einem ins Stocken geraten, ist das ein guter Hinweis — dann ist der Entwurf an dieser Stelle vielleicht nicht zu Ende gedacht.

14:33 Für die Schnelleren gibt es eine Zusatzaufgabe, die genau diese Frage noch einmal stellt: eine Filterung nach Preisgrenze. Überlegen Sie kurz, bevor Sie tippen, wohin dieser Parameter gehört. Der erste Punkt ist der häufigste Entwurfsfehler bei REST-Schnittstellen überhaupt: Man bildet die eigene innere Struktur ab statt Ressourcen, die für den Aufrufer Sinn ergeben.

14:55 Der zweite hängt damit zusammen — Verben in Pfaden. Wenn ein Pfad Wörter wie hole oder anlegen enthält, drückt er etwas aus, was die HTTP-Methode schon sagt. Und der dritte ist eine schlichte Prüflücke: Wer Antworten nur im Browser ansieht, bekommt den Statuscode nie zu Gesicht. Gerade der ist aber die Auskunft, die ein aufrufendes System zuerst auswertet.

Fehler behandeln

15:16 Und damit sind wir beim Statuscode, der nicht stimmt. Wir fragen gleich nach einem Termin, den es nicht gibt, und bekommen eine Antwort, die niemand haben will. Der Weg zur richtigen Antwort führt über drei Stufen. Was hier passiert, ist typisch und lehrreich. Die Methode ruft get auf einem leeren Optional auf, das wirft eine Ausnahme, die Ausnahme fliegt aus dem Controller heraus, und mangels Behandlung antwortet der Dienst mit Status fünfhundert — einem Serverfehler.

15:44 Nur ist es kein Serverfehler. Der Server hat alles richtig gemacht; es gibt den Termin schlicht nicht. Erwartet wird Status vierhundertvier. Der Unterschied ist nicht kosmetisch: Ein Serverfehler signalisiert dem Aufrufer, dass etwas kaputt ist und ein Wiederholungsversuch sinnvoll sein könnte. Vierhundertvier sagt, dass die Anfrage so nie funktionieren wird.

16:06 Diese Tabelle ist als Steigerung zu lesen. Der erste Weg funktioniert und ist die naheliegende Reaktion: Man ändert den Rückgabetyp und entscheidet in der Methode selbst über die Antwort. Volle Kontrolle, aber die Grenze steht rechts — man wiederholt das in jeder Methode. Der zweite Weg zieht die Behandlung aus den Methoden heraus an eine Stelle je Controller.

16:27 Deutlich besser, gilt aber nur dort. Der dritte hebt sie ganz heraus, für alle Controller gemeinsam. Wir zeigen gleich den zweiten Weg, weil er den Gedanken sichtbar macht. In größeren Anwendungen landen Sie beim dritten. Das Prinzip ist bemerkenswert schlicht: Sie schreiben eine Methode, sagen per Annotation, für welchen Ausnahmetyp sie zuständig ist, und Spring Web MVC ruft sie auf, wenn diese Ausnahme aus einer Methode dieses Controllers herausfliegt.

16:54 Was die Methode zurückgibt, wird zur Antwort. Beachten Sie, was dadurch aus dem Controller verschwindet: jede Prüfung auf leere Optionals. Die Methoden dürfen wieder den geradlinigen Fall beschreiben. Der Sonderfall ist an einer Stelle geregelt, und zwar so, dass er für alle Methoden gleich aussieht. Genau diese Einheitlichkeit ist im Betrieb mehr wert als jede einzelne Behandlung.

17:17 Der dritte Punkt ist der überzeugendste: Neue Methoden vergessen die Behandlung zuverlässig. Das ist keine Unterstellung, sondern Erfahrung — was man in jeder Methode wiederholen muss, wird irgendwann vergessen, besonders unter Zeitdruck. Ein zentraler Ort dreht das um: Die Behandlung gilt automatisch auch für Methoden, die es noch gar nicht gibt.

17:37 Und der vierte Punkt ist der, den Qualitätssicherung schätzt: Das Verhalten wird prüfbar. Sie können einen Test schreiben, der belegt, dass diese Art von Fehler zu dieser Art von Antwort führt — für den ganzen Controller, nicht je Methode. Der erste Punkt ist die Wurzel unseres ganzen Problems: get auf einem Optional aufzurufen, ohne sich zum leeren Fall zu verhalten.

17:59 Der zweite ist eine Sicherheitsfrage, die man leicht übersieht — Fehlerdetails, die interne Struktur verraten. Ein Stacktrace in der Antwort ist eine Einladung. Geben Sie nach außen so viel, dass der Aufrufer sein Problem versteht, und nicht mehr. Der dritte ist eine Frage des Maßes: Für jeden Ausnahmetyp einen eigenen Behandler zu schreiben, führt zu genau der Zersplitterung, die wir gerade beseitigt haben.

Anfragen validieren

18:23 Bisher hat Radwerk nur Auskunft gegeben. Jetzt nimmt es zum ersten Mal Daten entgegen — und damit stellt sich sofort die Frage, ob man diesen Daten trauen darf. Die Antwort ist erwartbar. Zwei Neuerungen stecken hier. Die erste ist eine Bequemlichkeit: Die Annotation für den Basispfad steht an der Klasse statt an jeder Methode.

18:44 Alle Methoden dieses Controllers hängen damit unter demselben Pfad, ohne ihn zu wiederholen. Die zweite ist substanziell: Beim Anlegen kommen Daten im Rumpf der Anfrage an, und die werden an einen Record gebunden. Wichtig ist, was in diesem Record steht — nämlich nur das, was ein Aufrufer wirklich schicken darf. Die Kennung und die Auftragsnummer gehören nicht dazu; die erzeugt die Anwendung selbst.

19:08 Der rote Faden hier lautet: drei Bausteine, und alle drei müssen sitzen. Die Abhängigkeit bringt die Prüfmaschine ins Projekt. Die Annotation am Parameter sagt, dass für diesen Aufruf geprüft werden soll. Und die Annotationen am Record beschreiben, was geprüft wird. Die Fußzeile nennt den Grund, warum ich das so betone: Fehlt einer der drei Schritte, wird stillschweigend nicht geprüft.

19:31 Es gibt keine Warnung, keine Fehlermeldung — die Anfrage geht einfach durch. Das ist der mit Abstand häufigste Grund für Validierung, die scheinbar wirkungslos ist. Der Reiz dieses Ansatzes liegt darin, dass die Regel dort steht, wo das Feld steht. Wer den Record liest, sieht sofort, was erwartet wird — es gibt keine zweite Datei und keine Prüfmethode, die man übersehen könnte.

19:54 Der Unterschied zwischen den beiden Annotationen ist übrigens praxisrelevant: Die eine verlangt nur, dass überhaupt ein Wert da ist. Die andere verlangt zusätzlich, dass es kein leerer Text ist und nicht nur Leerzeichen enthält. Für einen Namen ist das die richtige Wahl, denn eine Anmeldung auf drei Leerzeichen ist formal ein Wert, aber fachlich unbrauchbar.

20:15 Zwei Aufrufe, zwei Ergebnisse — mehr braucht diese Aufgabe nicht. Eine unvollständige Anfrage muss mit Status vierhundert abgelehnt werden, eine vollständige muss die Anmeldung anlegen. Der Zusatz ist aber der interessantere Teil: Schauen Sie ins Protokoll und vergleichen Sie, was der Dienst dort über den Prüffehler festhält und was er nach außen zurückgibt.

20:36 Sie werden feststellen, dass die Antwort deutlich wortkarger ist. Das ist die Voreinstellung, und sie ist als Sicherheitsentscheidung gemeint. Ob sie für Ihren Fall passt, ist eine bewusste Abwägung — dafür gibt es Wege, das zu ändern. Der erste Punkt ist die eben besprochene Falle mit den drei Bausteinen, und er ist der Grund, warum ich das Vorgehen so ausführlich gemacht habe.

20:59 Der zweite betrifft die Erwartung an die Antwort — dass Prüfdetails ohne weiteres Zutun erscheinen. Tun sie nicht. Der dritte ist eine Abgrenzungsfrage, die im Alltag oft schwerfällt: Fachliche Regeln gehören nicht in Prüfannotationen. Dass ein Name nicht leer sein darf, ist eine Formprüfung. Dass ein Termin ausgebucht ist, ist eine fachliche Aussage, die den Datenbestand kennen muss — die gehört hinter den Controller, nicht an den Record.

API-Versionierung

21:25 Zum Abschluss ein Thema, das in Spring Boot 4 neu unterstützt wird und das man erst zu schätzen weiß, wenn man einmal eine Schnittstelle gebrochen hat, die andere benutzen. Solange Sie allein an einer Schnittstelle arbeiten, ist alles einfach. Sobald andere sie benutzen, wird jede Änderung zu einer Verhandlung. Sie können nicht einfach ein Feld umbenennen — irgendwo läuft Code, der genau diesen Namen erwartet.

21:48 Versionierung ist der etablierte Ausweg: Die neue Fassung entsteht, die alte bleibt eine Weile erreichbar, und die Aufrufer bekommen Zeit zum Umstellen. Neu in Spring Boot 4 ist, dass Spring Web MVC die Version als Teil der Zuordnung kennt. Das klingt technisch, ist aber der entscheidende Unterschied — dazu gleich mehr.

22:08 Alle vier funktionieren, und die Wahl ist eher eine Frage des Stils als der Technik. Der Pfad ist am sichtbarsten — man sieht der Adresse an, welche Version gemeint ist, was beim Suchen von Fehlern hilft. Der Abfrageparameter ist am leichtesten zu setzen, wirkt aber wie ein Filter, was er nicht ist. Der eigene Kopf hält die Pfade sauber, macht die Version dafür unsichtbar.

22:29 Und die Variante im Accept-Kopf ist die standardnächste, weil sie ausdrückt, welche Darstellung der Aufrufer verarbeiten kann. Wichtig ist vor allem, dass Sie sich für eine entscheiden und dabei bleiben. Hier steht der Punkt, auf den es mir ankommt, gleich in der ersten Zeile: Die Version wird Teil der Zuordnungsinformation, nicht der Methodenlogik. Der Unterschied ist groß.

22:51 Ohne diese Unterstützung würden Sie in der Methode abfragen, welche Version gewünscht ist, und dann verzweigen — mit dem Ergebnis, dass beide Fassungen in einer Methode verwoben sind und keine mehr sauber entfernt werden kann. Mit der Unterstützung schreiben Sie zwei getrennte Methoden für denselben Pfad, und das Framework wählt aus. Die alte Fassung zu entfernen heißt dann: eine Methode löschen.

23:14 Der erste Punkt ist genau das eben beschriebene Verzweigen im Methodenrumpf. Der zweite ist ein Betriebsproblem, das schleichend entsteht: Man hält alte Versionen offen, weil niemand widerspricht, und pflegt irgendwann vier Fassungen parallel. Versionierung braucht deshalb immer auch eine Absprache, wie lange eine Fassung lebt.

23:32 Und der dritte Punkt ist die andere Übertreibung: nicht jede Änderung braucht eine neue Version. Ein zusätzliches Feld in der Antwort bricht in der Regel niemanden. Versionieren Sie, wenn Sie etwas wegnehmen oder umdeuten — nicht, wenn Sie etwas ergänzen.

Was Sie aus Modul 3 mitnehmen

23:48 Vier Dinge sollten bleiben. Der Controller nimmt entgegen und antwortet; die Logik liegt dahinter. Die Bindung entscheidet über die Gestalt der Schnittstelle — Pfad für Ressourcen, Abfrage für Suchkriterien, und diese Entscheidung ist dauerhaft. Fehlerbehandlung gehört an eine Stelle, sonst wird sie vergessen. Und Validierung braucht drei Bausteine; fehlt einer, prüft niemand, ohne dass es auffällt.

24:11 Im nächsten Modul ersetzen wir die fest verdrahteten Listen durch echte Datenbanken. Sie werden sehen, dass die Controller dabei unangetastet bleiben — genau dafür haben wir die Repositories eingezogen.

Lieber mit Trainer? Dieses Modul ist Teil unserer Team-Schulungen — mit Übungen, Ihrem eigenen Code und Fragen, die ein Video nicht beantwortet. Mehr erfahren →