Start / Seminare / Java SE in der Praxis

Modul

REST-API

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

REST-API

0:00 Kursradar sammelt inzwischen Kurse und legt sie in einer Datenbank ab. Nur: Sehen kann das niemand. Die Daten liegen in einer Datei, und wer sie lesen will, müsste die Kommandozeilenanwendung starten. In diesem Modul ändern wir das. Wir bauen einen Server, der die gesammelten Kurse über HTTP herausgibt — als JSON, so wie es andere Programme erwarten.

0:21 Und wir gehen einen Schritt weiter: Kolleginnen und Kollegen sollen Notizen an einen Kurs hängen können. Damit wird aus einer Sammlung ein kleiner Dienst, den ein Team tatsächlich benutzen kann.

REST-API

0:33 Sechs Stationen liegen vor uns. Zuerst klären wir, mit welchen Bausteinen wir arbeiten — und da begegnet uns ein Muster wieder, das wir schon kennen. Dann beschreiben wir eine Ressource, starten den Server und liefern JSON aus. Der Weg dorthin führt über zwei Fehlversuche, und die nehmen wir bewusst mit, weil sie mehr erklären als jede Erfolgsmeldung.

0:53 Zum Schluss ergänzen wir die Notizen — quer durch alle Schichten, vom Datentyp über die Datenbank bis zum Endpunkt. Sie werden sehen, wie sich die Trennung aus Modul drei jetzt auszahlt. Vier Ziele. Erstens eine REST-Schnittstelle beschreiben, und zwar mit den Mitteln, die im Java-Enterprise-Umfeld seit Jahren üblich sind.

1:13 Zweitens verstehen, warum Spezifikation und Umsetzung getrennte Abhängigkeiten sind — das ist kein Formalismus, sondern erklärt, warum Sie gleich fünf statt zwei Einträge in der Projektdatei haben. Drittens JSON ausliefern, ohne den Endpunkt dafür umzubauen. Und viertens einen schreibenden Zugang ergänzen und ihn prüfen — wofür der Browser diesmal nicht ausreicht.

1:34 Am Ende hat Kursradar zwei Zugänge: einen lesenden und einen schreibenden.

JAX-RS und Jersey

1:39 Fangen wir mit den Begriffen an, denn hier verwechselt man leicht zwei Dinge. Es gibt eine Spezifikation, und es gibt Umsetzungen davon. Wer das einmal sauber getrennt hat, versteht danach auch, warum die Fehlermeldungen manchmal von etwas sprechen, das man selbst nie eingebunden hat. JAX-RS ist eine Spezifikation. Sie beschreibt über Annotationen, welche Methode welchen HTTP-Aufruf beantwortet — und mehr nicht. Ausgeführt wird davon zunächst gar nichts.

2:07 Dafür braucht es eine Umsetzung, und wir nehmen Jersey, dazu einen HTTP-Server namens Grizzly. Wenn Ihnen dieses Muster bekannt vorkommt: ja, das ist dasselbe wie bei der Protokollierung im ersten Modul. Eine Schnittstelle, gegen die Sie programmieren, und eine austauschbare Umsetzung dahinter. In der Java-Welt begegnet Ihnen diese Trennung immer wieder — bei Datenbanken, beim Protokollieren, bei Nachrichtensystemen.

2:34 Der praktische Nutzen ist derselbe wie beim Protokollieren. Ihr Code kennt nur die Annotationen. Sollte Jersey eines Tages nicht mehr passen, tauschen Sie die Umsetzung, und die Endpunkte bleiben, wie sie sind. Ehrlicherweise passiert das selten — aber es gibt einen zweiten, alltäglicheren Gewinn: Sie können die Abhängigkeiten sauber trennen.

2:54 Gegen die Spezifikation wird übersetzt, mit der Umsetzung wird gelaufen. Was Sie im Code nie ansprechen, bekommt einen entsprechend engeren Geltungsbereich. Das ist Ordnung, die sich beim Ausliefern auszahlt — und die verhindert, dass sich jemand versehentlich an eine bestimmte Umsetzung bindet. Den Ablauf kennen Sie aus dem letzten Modul, deshalb geht es schnell. Zwei Punkte lohnen den Hinweis.

3:18 Der zweite Schritt — die Abhängigkeit auf das Datenmodul — ist der Beweis für die Arbeit aus Modul drei: Der Server bekommt Zugriff auf die Datenbank, ohne die Kommandozeilenanwendung zu kennen. Und der Fußhinweis zeigt eine echte Schwäche des jetzigen Stands: Wir kopieren dieselben Abhängigkeiten in die dritte Projektdatei. Das stört zu Recht.

3:38 Im letzten Modul räumen wir genau das auf — aber erst, wenn der Schmerz spürbar ist. Vorher wirkt jede Aufräumaktion wie Selbstzweck. Der erste Punkt ist der wichtigste: Typen der Umsetzung im eigenen Code ansprechen. Dann ist die Austauschbarkeit dahin, und zwar unbemerkt. Der zweite ist ein Anfängerfehler mit Ansage — ein neues Modul anlegen und die Abhängigkeit vergessen; die Fehlermeldung ist dann wenigstens eindeutig.

4:04 Der dritte betrifft eine Erwartung: Annotationen tun nichts von selbst. Ohne Umsetzung, die sie ausliest, sind sie Kommentare mit Klammern. Und der vierte greift zurück auf Modul drei: Zwei Anwendungen auf derselben Datenbankdatei brauchen die passende Betriebsart. Sonst sperrt die erste die zweite aus.

Eine Ressource beschreiben

4:23 Jetzt schreiben wir den ersten Endpunkt. Und wir machen das in zwei Etappen: erst so, dass überhaupt etwas zurückkommt, und danach so, dass es auch brauchbar ist. Dieser Zwischenschritt ist Absicht — er trennt zwei Fragen, die man sonst gleichzeitig lösen müsste. Eine Ressourcenklasse bündelt die Endpunkte eines Pfads. Am Klassenkopf steht das Präfix, an der Methode das HTTP-Verfahren.

4:47 Damit ist die Zuordnung zwischen Aufruf und Code komplett über Annotationen beschrieben — Sie schreiben keine Registrierung, keine Weiterleitungstabelle, nichts. Wichtig ist der letzte Satz: Das Repository kommt über den Konstruktor herein. Die Ressource erzeugt es nicht selbst. Das ist dieselbe Entscheidung wie beim Katalog-Service in Modul zwei, und sie hat denselben Grund: Was von außen hereingegeben wird, lässt sich im Test durch etwas anderes ersetzen.

5:15 Zehn Zeilen, und der erste Endpunkt steht. Beachten Sie, dass wir hier bewusst noch eine simple Zeichenkette zurückgeben statt JSON. Warum der Umweg? Weil damit erst einmal nur eine Frage im Raum steht — kommt der Aufruf überhaupt an? Wenn ja, ist die halbe Strecke geschafft, und wir können uns in Ruhe um das Antwortformat kümmern. Diese Aufteilung von Problemen ist eine Gewohnheit, die sich lohnt.

5:38 Wer Serveranbindung und Datenformat gleichzeitig baut und dann einen Fehler bekommt, weiß nicht, wo er suchen soll. Der erste Punkt ist der, der später weh tut: Das Repository in der Ressource selbst erzeugen. Dann ist die Klasse an die Datenbank genagelt und im Test nur noch mit laufender Umgebung zu prüfen. Der zweite ist ein Flüchtigkeitsfehler — Pfadangaben an Klasse und Methode doppeln sich, und der Endpunkt liegt woanders als gedacht.

6:05 Der dritte ist eine Frage der Zuständigkeit: In die Ressource gehört die Übersetzung zwischen HTTP und Anwendung, nicht die Fachlogik. Und der vierte ist ein Klassiker: Die Ressource existiert, ist aber nirgends angemeldet — dann antwortet der Server mit einem freundlichen Nichts. Ihre Aufgabe ist überschaubar und trotzdem lehrreich.

6:25 Beschreiben Sie den Endpunkt über Annotationen, geben Sie das Repository über den Konstruktor herein, und liefern Sie die Kurse zurück. Erfolgreich sind Sie, wenn Pfad und Verfahren an der Klasse beziehungsweise der Methode stehen und die Methode tatsächlich Daten liefert. Achten Sie bewusst darauf, dass Sie in dieser Klasse nichts über die Datenbank schreiben — kein Verbindungsaufbau, kein SQL.

6:47 Wenn Sie das Bedürfnis verspüren, ist meistens die Schichtung schief, nicht die Aufgabe.

Den Server starten

6:53 Eine Ressourcenklasse allein antwortet noch niemandem. Es fehlt der Teil, der zuhört. Bauen wir also den Server — und schauen uns dabei an, welche Bausteine dafür eigentlich zusammenkommen müssen. Der Server ist ein zweites Hauptprogramm neben der Kommandozeilenanwendung. Es öffnet das Repository, meldet die Ressource an einer Konfiguration an und startet darüber einen HTTP-Server.

7:16 Danach passiert etwas, was für viele ungewohnt ist: Das Programm endet nicht. Es läuft weiter und wartet. Wer bisher nur Anwendungen geschrieben hat, die etwas tun und sich verabschieden, sollte sich diesen Unterschied bewusst machen — er verändert auch, wie man über Ressourcen nachdenkt. Eine Verbindung, die in einem kurzen Lauf harmlos ist, wird in einem Prozess mit wochenlanger Laufzeit zum Problem.

7:40 Fünf Einträge für einen kleinen Server — das wirkt viel. Aber schauen Sie auf die zweite Spalte, dort steckt die Ordnung. Zwei Bausteine übersetzen Ihren Code: die Annotationen und der Kern der Umsetzung. Die anderen brauchen Sie nur zur Laufzeit, und Sie sprechen sie im Code nie an. Genau deshalb bekommen sie den engeren Geltungsbereich. Das ist dasselbe Prinzip wie bei der Protokoll-Umsetzung im ersten Modul, nur in größerem Maßstab.

8:06 Wenn Sie sich einmal angewöhnen, bei jeder Abhängigkeit kurz zu fragen, ob Ihr Code sie wirklich anfasst, vermeiden Sie eine Menge stiller Kopplung. Neun Zeilen, drei Schritte: Repository öffnen, Ressource anmelden, Server starten. Der Fußhinweis ist wichtiger, als er aussieht — es ist dieselbe Datenbankdatei, die auch die Kommandozeilenanwendung benutzt.

8:27 Deshalb haben wir in Modul drei die Betriebsart gewählt, die mehreren Prozessen den Zugriff erlaubt. Diese Art von Zusammenhang über Modulgrenzen hinweg ist typisch: Eine Entscheidung, die vor zwei Kapiteln beiläufig getroffen wurde, wird jetzt zur Voraussetzung. Und der letzte Punkt: Die Adresse und der Port stehen fest im Code. Auch das räumen wir im letzten Modul auf.

8:50 Was wir hier machen, ist Dependency Injection — nur eben von Hand. Wir erzeugen das Repository und reichen es der Ressource. In einem Rahmenwerk wie Spring würde das ein Container übernehmen, gesteuert über Annotationen. Der Vorteil unserer Variante: Man sieht, wer was erzeugt und wer womit arbeitet. Es bleibt gewöhnlicher Java-Code, ohne Magie. In größeren Anwendungen wird das mühsam, und dann nimmt man ein Rahmenwerk — aber das Muster darunter bleibt genau dieses.

9:20 Wer es einmal von Hand gebaut hat, versteht später, was der Container eigentlich tut. Der erste Punkt trifft im Seminar fast jede Gruppe: Port achttausendachtzig ist schon belegt, weil irgendein anderes Werkzeug ihn benutzt. Die Meldung steht im Protokoll, man muss sie nur lesen. Der zweite ist eine falsche Erwartung an die eingebettete Datenbank — sie endet mit dem Prozess.

9:43 Der dritte ist verwandt mit dem ersten: zwei Serverprozesse starten statt den laufenden neu zu starten. Und der vierte betrifft die Ausgabe: Sie werden zwei verschiedene Protokollformate untereinander sehen. Das ist kein Fehler, sondern hat einen Grund — und wir lösen es im letzten Modul.

JSON ausliefern

10:00 Der Server läuft und antwortet mit einer Zeichenkette. Für Menschen ist das lesbar, für Programme unbrauchbar. Machen wir daraus JSON. Der Weg dahin führt über zwei Fehlermeldungen — und ich schlage vor, wir gehen ihn genau so, statt gleich die fertige Lösung hinzuschreiben. Eine annotierte Methode darf nicht nur Zeichenketten zurückgeben, sondern auch Objekte. Die Umsetzung baut daraus die Antwort.

10:24 Damit daraus JSON wird, braucht es allerdings zweierlei, und beides fehlt zunächst: eine Angabe an der Methode, welches Format herauskommen soll, und eine Bibliothek, die das Umwandeln tatsächlich erledigt. Interessant ist, dass die Spezifikation nur das Erste kennt. Das Zweite ist Sache der Umsetzung — und genau deshalb bekommen Sie eine Fehlermeldung, in der ein Begriff vorkommt, den Sie noch nie eingebunden haben.

10:49 Diese fünf Schritte sind das eigentliche Lehrstück dieses Kapitels. Sie ändern den Rückgabetyp, rufen auf — und bekommen einen Serverfehler. Im Protokoll steht, dass keine Umwandlung für einfachen Text gefunden wurde. Sie geben also das gewünschte Format an, rufen erneut auf — und bekommen wieder einen Fehler, aber diesmal steht ein anderer Medientyp darin.

11:11 Genau daran erkennen Sie, dass Sie einen Schritt weiter sind. Erst der dritte Schritt löst es: eine Brücke zwischen der Umsetzung und der JSON-Bibliothek. Die Meldung sagt Ihnen jedes Mal, wo Sie stehen. Man muss sie nur ernst nehmen. Der Endpunkt selbst bleibt fast unverändert — das ist die gute Nachricht. Zwei Ergänzungen sind interessant. Die Sortierung sorgt dafür, dass die Antwort vorhersagbar ist.

11:35 Ohne sie wechselt die Reihenfolge je nach Laune der Datenbank, und dann schreibt irgendwann jemand einen Test, der mal grün und mal rot ist. Die zweite Ergänzung ist die Fehlerbehandlung: Wir fangen die eigene Repository-Ausnahme ab und übersetzen sie in einen HTTP-Statuscode. Ob ausgerechnet vierhundertvier die richtige Wahl ist, kann man diskutieren — dass man diese Entscheidung bewusst trifft, nicht.

12:00 Der erste Punkt ist der häufigste im Umgang mit Schnittstellen: Fehlerfälle offenlassen, sodass jede Störung als allgemeiner Serverfehler herausgeht. Der Aufrufer weiß dann nur, dass etwas kaputt ist. Der zweite hängt daran — die eigene Ausnahme unübersetzt durchreichen. Der dritte ist die Reihenfolge, über die wir gerade gesprochen haben: unsortiert ausliefern und sie trotzdem für stabil halten.

12:23 Und der vierte betrifft die JSON-Brücke: Sie gehört an die Laufzeit, nicht ans Übersetzen. Wenn Sie gegen sie programmieren, binden Sie sich an eine Umsetzung, die Sie eigentlich nur benutzen wollten. Jetzt sind Sie dran, und ich empfehle ausdrücklich, den Weg über die Fehler zu gehen statt gleich alles einzubinden. Erfolgreich sind Sie, wenn ein Aufruf ein JSON-Array mit stabiler Reihenfolge liefert. Und dann kommt der Teil, den man leicht überspringt: Prüfen Sie auch den Fehlerfall.

12:51 Machen Sie die Datenbank unerreichbar — etwa indem Sie die Datei umbenennen — und schauen Sie, was der Aufrufer bekommt. Ein Dienst ist erst dann fertig, wenn er auch dann eine sinnvolle Antwort gibt, wenn etwas schiefläuft.

Notizen im Repository

13:04 Kommen wir zur zweiten Funktion: Notizen an einem Kurs. Das ist fachlich eine Kleinigkeit und technisch ein guter Anlass, einmal zu sehen, wie sich eine Änderung durch alle Schichten zieht — vom Datentyp über die Datenbank bis zum Endpunkt. Ein Kurs bekommt eine Notiz. Und zwar optional, denn beim Import aus dem Katalog gibt es noch keine.

13:24 Das ist ein wichtiger Modellierungsschritt: Wir sagen ausdrücklich, dass dieses Feld fehlen darf, statt es einfach leer zu lassen. Java bietet dafür einen eigenen Typ an, und der zwingt jeden Aufrufer, sich mit der Möglichkeit auseinanderzusetzen. Der Datentyp wächst also um eine Komponente, das Repository um eine Methode. Und beides zieht Anpassungen an allen Stellen nach sich, die den Typ verwenden.

13:49 Genau das schauen wir uns jetzt an — es ist lehrreicher als die Notiz selbst. Zwei Dinge passieren hier gleichzeitig. Der Record bekommt eine fünfte Komponente, und die Prüfung im Konstruktor wächst mit: Wenn eine Notiz da ist, darf sie nicht leer sein. Das ist ein feiner, aber wichtiger Unterschied — keine Notiz ist erlaubt, eine leere dagegen nicht.

14:10 Und jetzt kommt der Teil, der im Seminar oft überrascht: Diese Änderung ist nicht abwärtskompatibel. Der Compiler zeigt Ihnen sofort jede Stelle, die den Konstruktor benutzt. Das ist unbequem und gleichzeitig ein Geschenk — Sie bekommen eine vollständige Liste dessen, was Sie anfassen müssen. Diese Methode wird Ihnen bekannt vorkommen, und das ist der Punkt. Dieselbe Ressourcenverwaltung, dieselben Platzhalter, dieselbe Übersetzung der Ausnahme — nur ein anderes SQL.

14:39 Genau bei solchen Zwillingen lohnt der zweite Blick: Zwei fast gleiche Methoden nebeneinander sind eine Einladung, das Gemeinsame herauszuziehen. Ich sage bewusst nicht, dass Sie das sofort tun müssen. Bei zwei Methoden ist das Verhältnis von Aufwand zu Nutzen grenzwertig. Bei der dritten wird es eindeutig. Behalten Sie es im Hinterkopf — es steht am Ende dieses Kapitels auch als Stolperstein.

15:03 Der erste Punkt ist der, den wir eben modelliert haben: eine leere Zeichenkette als Notiz zulassen und sie für vorhanden halten. Der zweite betrifft die Datenbank — die Spalte muss leer bleiben dürfen, sonst kann der Import gar nicht funktionieren. Der dritte ist interessant für Testcode: Unsere Ersatzumsetzung aus Modul drei kennt die neue Methode nicht.

15:24 Sie still mitwachsen zu lassen, wäre bequem; besser ist es, sie ausdrücklich fehlschlagen zu lassen, wenn jemand sie benutzt. Dann merkt man es. Und der vierte sind die erwähnten Zwillinge.

Notizen über die API annehmen

15:35 Das Fundament liegt, jetzt kommt der Endpunkt. Und mit ihm ein erster schreibender Zugang — was bedeutet, dass wir zum Prüfen den Browser verlassen müssen. Der kann nämlich nur eine Sorte Anfragen stellen. Dieser Endpunkt nimmt entgegen, statt zu liefern. Er beantwortet ein POST auf einen Pfad, in dem ein Platzhalter für die Kennung steckt.

15:57 Zwei Informationen kommen also aus zwei verschiedenen Quellen: die Kennung aus dem Pfad, der eigentliche Text aus dem Rumpf der Anfrage. Zurück gibt er nichts. Das ist typisch für schreibende Zugänge — der Aufrufer will wissen, ob es geklappt hat, und diese Information steckt im Statuscode, nicht im Inhalt. Sie werden gleich sehen, dass hier mehr Annotationen stehen als Programmzeilen. Das ist in der Java-Welt normal, gewöhnungsbedürftig bleibt es trotzdem.

16:24 Sieben Zeilen, davon vier Annotationen und eine einzige, die etwas tut. Das ist der Stil, den viele Java-Rahmenwerke pflegen: Die Beschreibung steht deklarativ darüber, die Arbeit passiert darunter. Der Vorteil ist Kürze und Lesbarkeit — man sieht auf einen Blick, was dieser Endpunkt annimmt. Der Preis ist, dass man die richtige Kombination von Annotationen kennen muss, und die findet man nicht durch Nachdenken, sondern nur in der Dokumentation.

16:51 Wichtig noch: Der Pfad ist relativ zur Klasse. Vollständig ergibt sich daraus die Adresse mit der Kennung in der Mitte. Für einen schreibenden Aufruf brauchen wir ein anderes Werkzeug. Curl ist auf den meisten Systemen vorhanden und tut genau das: eine HTTP-Anfrage bauen und abschicken. Drei Angaben sind nötig — das Verfahren, der Inhaltstyp und der Rumpf.

17:13 Und der Inhaltstyp ist die Stelle, an der es am häufigsten hakt: Er muss zu dem passen, was der Endpunkt entgegennimmt. Stimmt er nicht, antwortet der Server mit einem Code, der auf ein nicht unterstütztes Format hinweist. Wer diese Meldung einmal gesehen hat, weiß beim nächsten Mal sofort, wo er nachschauen muss. Ihre Aufgabe hat zwei Teile, und der zweite ist der eigentliche. Schicken Sie eine Notiz an einen Kurs.

17:38 Und dann rufen Sie die Übersicht ab und schauen nach, ob sie dort auch ankommt. Erfolgreich sind Sie erst mit diesem zweiten Aufruf — dass ein Befehl ohne Fehlermeldung durchläuft, beweist noch gar nichts. Sehr wahrscheinlich stolpern Sie dabei über eine Meldung, dass sich ein optionaler Wert nicht umwandeln lässt. Das ist kein Zufall, sondern die letzte kleine Lücke: Der JSON-Bibliothek fehlt eine Erweiterung für genau diesen Typ.

18:03 Der erste Punkt ist der eben erwähnte Inhaltstyp. Der zweite ist ein Entwurfsfehler mit Folgen: eine schreibende Aktion über ein lesendes Verfahren anbieten, weil das im Browser bequemer ist. Das bricht Erwartungen, die Zwischenspeicher und Suchmaschinen an HTTP haben — und kann echten Schaden anrichten. Der dritte betrifft ein Detail, das Sie in diesem Modul gestreift haben: Es können zwei Stände derselben Bibliothek im Projekt landen, weil eine davon mitgezogen wird.

18:30 Und der vierte ist die Haltung, die wir in der Aufgabe geübt haben: Erfolg misst man am Ergebnis, nicht am ausbleibenden Fehler.

Kursradar ist erreichbar

18:38 Bilanz. Wir haben ein Servermodul, das auf dieselben Daten zugreift wie die Kommandozeilenanwendung — ohne sie zu kennen. Die Endpunkte sind annotierte Methoden, die Umsetzung liegt austauschbar dahinter. Es gibt einen lesenden und einen schreibenden Zugang, beide mit definierten Fehlerfällen. Damit ist Kursradar funktional fertig.

18:58 Was fehlt, merkt man erst, wenn man die Anwendung jemandem geben will: Die Protokollausgabe ist uneinheitlich, Einstellungen stehen im Code, und ohne Entwicklungsumgebung startet nichts. Genau das nehmen wir uns im letzten Modul vor.

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