HTTP kompakt
HTTP ist die Sprache, in der Client und Server miteinander reden. Jeder Aufruf einer REST-API ist eine HTTP-Nachricht. Wer HTTP versteht, kann jede REST-API lesen.
(Andere Baustile sind nicht darauf festgelegt — SOAP kann seine Nachrichten auch über andere Wege schicken. Für REST ist HTTP nicht Beiwerk, sondern Teil des Konzepts.)
Ein Aufruf besteht aus zwei Nachrichten
Mehr passiert nicht. Der Client fragt, der Server antwortet, die Anfrage ist damit abgeschlossen. Für die nächste Frage beginnt alles von vorn — genau das meint Zustandslosigkeit.
Die Verbindung bleibt in der Regel bestehen: HTTP/1.1 hält sie offen, damit nicht für jedes Bild und jede Anfrage neu verbunden werden muss.
Zustandslosigkeit ist eine Aussage über etwas anderes — über das Gedächtnis des Servers. Er merkt sich zwischen zwei Anfragen nichts über den Gesprächsverlauf, auch wenn beide über dieselbe Verbindung laufen.
Aufbau einer Anfrage
POST /api/v1/persons HTTP/1.1 ← Startzeile: Methode, Pfad, Version
Host: localhost:8080 ┐
Content-Type: application/json │ Header (Kopfzeilen)
Accept: application/json ┘
← Leerzeile trennt Kopf und Rumpf
{"firstname":"Anna","surname":"Schmidt"} ← Body (Rumpf, optional)
| Teil | Bedeutung |
|---|---|
| Methode | Was soll passieren? GET, POST, PUT, DELETE … |
| Pfad | Womit? Die Adresse der Ressource |
| Header | Zusatzangaben — Datenformat, Anmeldung, Sprache … |
| Body | Die mitgeschickten Daten. Bei GET und DELETE normalerweise leer |
Aufbau einer Antwort
HTTP/1.1 201 Created ← Statuszeile: Code und Kurztext
Content-Type: application/json ┐ Header
Location: /api/v1/persons/1 ┘
← Leerzeile
{"firstname":"Anna","id":1,"surname":"Schmidt"} ← Body
Die HTTP-Methoden
| Methode | Zweck | CRUD | Body in der Anfrage? |
|---|---|---|---|
GET | Daten abrufen | Read | nein |
POST | Neue Ressource anlegen | Create | ja |
PUT | Ressource vollständig ersetzen | Update | ja |
PATCH | Ressource teilweise ändern | Update | ja |
DELETE | Ressource löschen | Delete | nein |
PUT schickt den kompletten neuen Stand — fehlende Felder werden geleert.
PATCH schickt nur die Änderung — alles andere bleibt.
Für eine Person mit Vor- und Nachnamen:
PUTmit{"firstname":"Anna"}→ der Nachname wäre danach leer.PATCHmit{"firstname":"Anna"}→ der Nachname bleibt stehen.
In diesen Tutorials verwenden wir PUT.
Statuscodes
Jede Antwort trägt einen dreistelligen Code. Die erste Ziffer sagt schon das Wichtigste:
| Gruppe | Bedeutung | Merkhilfe |
|---|---|---|
| 1xx | Information | „Moment noch…" |
| 2xx | Erfolg | „Hat geklappt." |
| 3xx | Umleitung | „Schau woanders nach." |
| 4xx | Fehler beim Client | „Du hast Mist gebaut." |
| 5xx | Fehler beim Server | „Ich habe Mist gebaut." |
4xx heißt: Es lag an der Anfrage — falsche Adresse, fehlende Daten, keine Berechtigung. Dieselbe Anfrage unverändert zu wiederholen hilft meist nicht.
„Meist", weil der genaue Code entscheidet: Nach einer Anmeldung verschwindet ein 401, nach dem Ablauf einer Sperre ein 429, und ein 404 kann sich in ein 200 verwandeln, sobald jemand den Datensatz anlegt.
5xx heißt: Der Server ist schuld. Dieselbe Anfrage kann gleich schon funktionieren.
Die Codes, die du brauchst
| Code | Text | Wann |
|---|---|---|
200 | OK | Anfrage erfolgreich, Antwort enthält Daten |
201 | Created | Ressource wurde angelegt. Sollte im Location-Header die Adresse der neuen Ressource mitschicken |
204 | No Content | Erfolgreich, aber es gibt nichts zurückzugeben — typisch nach DELETE |
400 | Bad Request | Die Anfrage ist fehlerhaft aufgebaut |
401 | Unauthorized | Nicht angemeldet. (Der Name ist irreführend — es geht um Authentifizierung.) |
403 | Forbidden | Angemeldet, aber nicht berechtigt |
404 | Not Found | Die angeforderte Ressource gibt es nicht |
405 | Method Not Allowed | Ressource existiert, aber nicht mit dieser Methode |
406 | Not Acceptable | Der Server kann kein Format liefern, das der Client im Accept-Header verlangt |
409 | Conflict | Widerspruch zum aktuellen Zustand, z. B. E-Mail schon vergeben |
415 | Unsupported Media Type | Der Server versteht das Format des mitgeschickten Bodys nicht |
500 | Internal Server Error | Unerwarteter Fehler im Server |
Ein Server, der bei einem nicht gefundenen Datensatz 200 OK mit dem Inhalt null zurückgibt.
Der Client muss dann raten, ob alles gut ging. Genau dafür gibt es 404.
Wichtige Header
| Header | Richtung | Bedeutung |
|---|---|---|
Content-Type | beide | In welchem Format ist der Body dieser Nachricht? |
Accept | Anfrage | In welchem Format hätte ich die Antwort gern? |
Authorization | Anfrage | Anmeldedaten, meist ein Token |
Location | Antwort | Adresse der neu angelegten Ressource (bei 201) |
Content-Type beschreibt, was ich mitschicke.
Accept beschreibt, was ich zurückhaben will.
Bei einem POST stehen oft beide in der Anfrage: „Ich schicke JSON und möchte JSON zurück."
Content Negotiation: mehrere Formate
Content-Type und Accept sind kein Selbstzweck. Über sie handeln Client und Server aus, in welchem Format die Daten übertragen werden. Dieses Aushandeln heißt Content Negotiation (Inhaltsverhandlung).
Solange eine API nur JSON kennt, gibt es nichts zu verhandeln. Interessant wird es, sobald ein Endpunkt mehrere Formate bedienen kann — etwa JSON für die App, CSV für den Export in die Tabellenkalkulation und XML für einen älteren Partnerdienst:
Dieselbe Ressource, dieselbe URL — das Format bestimmt der Client über den Accept-Header. In Spring gibt man die möglichen Formate am Endpunkt an:
@GetMapping(value = "/{id}",
produces = { MediaType.APPLICATION_JSON_VALUE,
MediaType.APPLICATION_XML_VALUE })
public Person getPersonById(@PathVariable Long id) { ... }
Für die Gegenrichtung — welche Formate der Endpunkt annimmt — gibt es consumes:
@PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
Was passiert, wenn das Format nicht passt?
Zwei Statuscodes gehören genau hierher, und sie sind leicht zu verwechseln:
| Code | Wer hat das falsche Format? | Auslöser |
|---|---|---|
415 Unsupported Media Type | Der Client schickt etwas, das der Server nicht lesen kann | Content-Type: text/plain bei einem JSON-Endpunkt |
406 Not Acceptable | Der Server kann nicht liefern, was der Client verlangt | Accept: application/xml bei einem JSON-Endpunkt |
Merkhilfe: 415 betrifft die Hinfahrt (den Request-Body), 406 die Rückfahrt (die Antwort).
Beide Codes kannst du an deiner eigenen Anwendung auslösen, ohne eine Zeile zu ändern:
# 415 – falsches Format geschickt
curl -i -X POST http://localhost:8080/api/v1/persons \
-H "Content-Type: text/plain" -d "abc"
# 406 – unmögliches Format verlangt
curl -i -H "Accept: application/xml" http://localhost:8080/api/v1/persons/1
Interessant dabei: Deine Endpunkte tragen kein consumes und kein produces — und liefern die richtigen Codes trotzdem. Spring erzwingt den Vertrag also schon von sich aus, weil JSON das einzige eingerichtete Format ist.
Genau deshalb sind die beiden Angaben im Normalfall verzichtbar. Sie werden erst nötig, wenn es wirklich mehr als ein Format gibt.
Fehlerantworten nach RFC 9457
Für Fehler gibt es ein standardisiertes Antwortformat namens Problem Details. Es ist am Content-Type application/problem+json erkennbar:
{
"type": "about:blank",
"title": "Not Found",
"status": 404,
"detail": "Keine Person mit der Id 99",
"instance": "/api/v1/persons/99"
}
| Feld | Bedeutung |
|---|---|
type | URI, die den Fehlertyp beschreibt |
title | Kurze, allgemeine Bezeichnung |
status | Der HTTP-Statuscode, noch einmal im Rumpf |
detail | Die konkrete, auf diesen Fall bezogene Meldung |
instance | Welche Anfrage den Fehler ausgelöst hat |
Der Vorteil gegenüber einer selbst erfundenen Struktur: Andere Programme und Werkzeuge kennen dieses Format bereits.
HTTP sichtbar machen
| Werkzeug | Wofür |
|---|---|
| Browser-Adresszeile | Nur GET. Schnellster Weg für einen Blick |
| Entwicklerwerkzeuge (F12), Reiter Netzwerk | Zeigt zu jeder Anfrage Methode, Status, Header und Body |
.http-Datei in der IDE | Requests als Textdatei speichern und ausführen — versionierbar |
| Postman | Eigenständiges grafisches Werkzeug, in Betrieben verbreitet. Sammelt Anfragen in „Collections". In diesen Tutorials nicht nötig — was es kann, decken die .http-Datei und die Swagger-Oberfläche ab. Wenn es dir im Betrieb begegnet: Es ist dasselbe HTTP darunter. |
curl | Kommandozeile, überall vorhanden, gut für Skripte |
Dieselbe Anfrage in drei Werkzeugen:
### .http-Datei
POST http://localhost:8080/api/v1/persons
Content-Type: application/json
{"firstname":"Anna","surname":"Schmidt"}
# curl
curl -X POST http://localhost:8080/api/v1/persons \
-H "Content-Type: application/json" \
-d '{"firstname":"Anna","surname":"Schmidt"}'
- Eine HTTP-Nachricht besteht aus Startzeile, Headern, Leerzeile und Body.
- Die Methode sagt, was passieren soll; der Pfad, womit.
- Die erste Ziffer des Statuscodes trennt 4xx (Client schuld) von 5xx (Server schuld).
Content-Typebeschreibt das Mitgeschickte,Acceptdas Gewünschte.- Über diese beiden Header handeln Client und Server das Format aus (Content Negotiation).
415meint ein unlesbares Format auf der Hinfahrt,406ein unmögliches auf der Rückfahrt. - Fehler werden standardisiert als Problem Details (
application/problem+json) zurückgegeben.
Weiterlesen
- Fehlerantworten — welcher Statuscode wann gilt und was in den Rumpf gehört
- JSON — das Format, das im Rumpf steckt
- Das REST-Paradigma — warum die Methoden gerade so heißen