Eine API soll standardmäßig JSON liefern, aber manche Clients möchten XML. Eine Website soll Deutsch zeigen, wenn der Browser Deutsch bevorzugt. Und ein Bild-Endpoint soll AVIF liefern, wenn der Browser es kann, sonst WebP oder JPEG. Genau für solche Fälle gibt es Content Negotiation (Inhaltsaushandlung) in HTTP: Client und Server einigen sich über Header darauf, welches Format, welche Sprache oder welche Codierung am besten passt.
In der Praxis wird das Thema oft unterschätzt. Dann entstehen Endpoints wie /api/v1/data.json, ?lang=de oder separate Routen pro Format. Das funktioniert, ist aber häufig schwer zu pflegen und führt zu Inkonsistenzen. Mit sauberer Aushandlung über Header bleibt die URL stabil und die Regeln sind an einer Stelle konzentriert.
WofĂĽr Content Negotiation im Alltag wirklich genutzt wird
Formatwahl: JSON, HTML, XML oder CSV
Die bekannteste Variante ist die Formatwahl ĂĽber den Accept-Header. Ein Client sagt damit, welche Medien-Typen (MIME Types) er akzeptiert, zum Beispiel:
- application/json fĂĽr JSON
- text/html fĂĽr HTML
- application/xml fĂĽr XML
- text/csv fĂĽr CSV
Wichtig: Der Server entscheidet am Ende. Der Client äußert nur Präferenzen. Wenn ein Format nicht angeboten wird, sollte der Server das klar signalisieren (dazu später mehr).
Sprachen: Deutsch vs. Englisch
Für Sprache ist Accept-Language zuständig, etwa de-DE,de;q=0.9,en;q=0.8. Browser schicken diesen Header automatisch. Der Server kann damit zum Beispiel:
- die richtige Übersetzung wählen,
- Datums-/Zahlenformate lokalisiert ausgeben,
- Fallbacks nutzen (z. B. erst de-DE, dann de, dann en).
Wenn eine App zusätzlich eine explizite Spracheinstellung im Profil hat, kann diese Vorrang bekommen. Content Negotiation ist dann ein guter Default, aber nicht die einzige Quelle.
Kompression und Varianten (kurzer Ăśberblick)
Es gibt weitere Formen, z. B. Aushandlung von Kompression über Accept-Encoding oder Bildformaten über Accept (z. B. image/avif). Diese Logik ist ähnlich, nur der Fokus ist ein anderer. Der Kern bleibt: Client sendet Präferenzen, Server wählt eine passende Variante.
So lesen Server die Header: Werte, Prioritäten und q-Faktoren
Was bedeutet q=0.8?
In Accept-Headern können Prioritäten über sogenannte Qualitätsfaktoren (q-Werte) angegeben werden. Beispiel:
- Accept: application/json;q=1.0, application/xml;q=0.8, */*;q=0.1
Das bedeutet: JSON ist am liebsten, XML geht auch, und „irgendwas“ ist nur Notlösung. Wenn kein q angegeben ist, gilt implizit q=1.0.
Wildcards sind erlaubt – aber tückisch
Viele HTTP-Clients senden standardmäßig Accept: */*. Das heißt nicht „liefere alles“, sondern „mir ist das Format egal“. Für APIs ist es sinnvoll, dann einen klaren Standard zu definieren (meist JSON) und nicht zufällig HTML zu liefern, nur weil derselbe Host auch eine Website ausliefert.
Warum Reihenfolge allein nicht reicht
Manche Entwickler verlassen sich auf die Reihenfolge in der Header-Liste. Das ist riskant: Ausschlaggebend ist primär q, nicht die Position. Eine robuste Implementierung wertet q korrekt aus und nutzt Reihenfolge nur als Tie-Breaker (wenn zwei Optionen den gleichen q-Wert haben).
Robuste Umsetzung auf dem Server: Regeln, Defaults, klare Fehler
Schritt 1: UnterstĂĽtzte Varianten definieren
Startpunkt ist immer eine feste Liste von Varianten, die der Server wirklich liefern kann. Zum Beispiel:
- Format: application/json, text/csv
- Sprache: de, en
Diese Liste sollte nicht „theoretisch“ sein, sondern tatsächlich implementiert. Sonst entstehen Situationen, in denen ein Client ein Format korrekt anfragt, aber der Server unvollständige Daten liefert.
Schritt 2: Sinnvolle Defaults festlegen
Wenn der Client keine Präferenz sendet (oder nur */*), braucht es einen Default. Typisch:
- API: JSON als Standard
- Website: HTML als Standard
- Sprache: z. B. Deutsch oder Englisch, abhängig vom Projekt
Wichtig ist die Konsistenz: Ein Endpoint sollte nicht je nach Client „mal so, mal so“ wirken. Wenn eine Route eine API ist, sollte sie im Zweifel immer API-Verhalten zeigen.
Schritt 3: Fehlersituationen sauber abbilden
Wenn der Client ausschließlich Formate anfragt, die nicht unterstützt werden, ist ein „best effort“ oft keine gute Idee. Dann ist ein klarer HTTP-Fehler hilfreicher. Bei Formatwahl ist dafür üblich:
- HTTP 406 (Not Acceptable), wenn kein angefragtes Format lieferbar ist
Für Sprache gilt das strenger selten, weil ein Fallback (z. B. auf Englisch) meist akzeptabel ist. Dennoch sollte die Anwendung einheitliche Regeln haben: Entweder Sprache ist „verhandelbar mit Fallback“, oder Sprache ist „hart“ und führt zu einer Fehlermeldung.
Praktische Schritte fĂĽr ein sauberes Setup
- Auf dem Server eine kleine Funktion bauen, die den Accept-Header gegen eine Whitelist matcht (inklusive q-Werten).
- FĂĽr APIs einen Default (z. B. JSON) definieren, wenn Accept fehlt oder */* ist.
- Bei Sprache zuerst eine explizite Einstellung (z. B. Benutzerprofil) berĂĽcksichtigen, danach Accept-Language, danach Fallback.
- Bei nicht unterstützten Formaten konsequent mit 406 antworten, statt „irgendwas“ zu liefern.
- Die tatsächlich gewählte Variante im Response klar machen (Content-Type setzen, ggf. Content-Language).
Typische Stolperfallen in APIs und Webapps
Format in die URL packen: schnell gebaut, schwer gepflegt
URLs wie /resource.json oder /resource.xml sind simpel, aber führen schnell zu doppelten Routen und doppelter Dokumentation. Außerdem wird Caching unübersichtlich, weil unterschiedliche Varianten zwar unterschiedliche URLs haben, aber fachlich denselben „Ressourcen-Namen“ tragen.
Sprache per Query-Parameter: okay, aber mit klaren Regeln
?lang=de kann sinnvoll sein (z. B. für sharebare Links). Dann sollte aber klar sein, wie es mit Accept-Language zusammenspielt: Query überschreibt Header, oder umgekehrt. Ohne feste Priorität entstehen schwer reproduzierbare Bugs.
Cache-Fallen: Varianten brauchen saubere Trennung
Wenn ein Endpoint je nach Accept oder Accept-Language unterschiedliche Inhalte liefert, muss Caching das berücksichtigen. Sonst kann es passieren, dass ein Cache Deutsch speichert und später Englisch ausliefert (oder umgekehrt). In solchen Fällen ist der HTTP-Header Vary entscheidend: Er sagt Caches, dass die Antwort je nach bestimmten Request-Headern variiert. Typisch sind Vary: Accept und/oder Vary: Accept-Language.
Wer bereits Caching nutzt, kann das Thema gut mit HTTP Caching: Cache-Control und ETag richtig nutzen zusammendenken, weil Varianten und Cache-Strategie zusammengehören.
Mini-Fallbeispiel: Ein Endpoint, zwei Formate, zwei Zielgruppen
Ausgangslage
Ein Team betreibt einen Endpoint /reports. Intern nutzt das Dashboard JSON. Externe Partner möchten regelmäßig CSV importieren. Anfangs gab es zwei Routen: /reports (JSON) und /reports.csv (CSV). Das führte zu doppelter Authentifizierung, doppelter Dokumentation und zwei Fehlerquellen.
Lösung über Accept
Beide Clients nutzen dieselbe URL, aber senden unterschiedliche Header:
- Dashboard: Accept: application/json
- Partner: Accept: text/csv
Der Server liefert die passende Repräsentation und setzt Content-Type entsprechend. Wenn ein Client etwas Unbekanntes anfragt, kommt 406 mit einer klaren Fehlermeldung. Ergebnis: weniger Routen, weniger Dokumentationsaufwand, und Format-Erweiterungen sind einfacher.
Vergleich: Header-Aushandlung vs. separate Endpoints
| Ansatz | Vorteile | Nachteile |
|---|---|---|
| Header (Accept, Accept-Language) | Eine URL pro Ressource, klare Standards, gut erweiterbar | Erfordert saubere Implementierung (q, Vary), Debugging über Header nötig |
| Separate URLs pro Format | Schnell zu verstehen, leicht manuell im Browser testbar | Doppelte Routen/Docs, häufiger Drift zwischen Varianten |
| Query-Parameter (z. B. ?format=csv) | Sharebare Links, einfach in Tools | Oft Mischformen ohne klare Priorität, kann mit Caching kollidieren |
Wie das mit API-Design und Fehlern zusammenhängt
Klare Antworten statt „zufällig passend“
Content Negotiation ist kein Ersatz für gutes API-Design, aber ein Baustein davon. Wer Formate aushandelt, sollte auch Fehler konsistent modellieren: Wenn ein Client ein falsches Format anfragt, ist das ein anderes Problem als „falsche Daten gesendet“. Für die grundsätzliche Fehlerlogik hilft API-Fehler richtig behandeln.
Security: Nicht alles akzeptieren, was ein Client behauptet
Header sind Eingaben. Sie sollten daher nicht blind vertrauenswürdig sein. Statt „Accept: application/pdf“ automatisch zu bedienen, sollte der Server nur aus bekannten, implementierten Varianten wählen. Das reduziert Angriffsfläche und verhindert unerwartete Content-Type-Ausgaben.
Häufige Fragen aus der Praxis
Reicht es, immer JSON zu liefern und Accept zu ignorieren?
Für viele private APIs ja, solange es dokumentiert und konsistent ist. Spätestens wenn mehrere Clients oder Export-Formate dazukommen, lohnt sich Content Negotiation, weil sie spätere Änderungen ohne neue URLs ermöglicht.
Sollte Sprache ĂĽber Accept-Language oder ĂĽber einen Parameter gesteuert werden?
Für Websites ist Accept-Language ein guter Start, weil Browser ihn automatisch senden. Für Apps mit Nutzerprofil ist eine explizite Einstellung oft besser. Wenn Links teilbar sein müssen, ist ein Parameter sinnvoll, aber mit klarer Prioritätsregel.
Kann Content Negotiation Breaking Changes vermeiden?
Sie kann helfen, neue Formate parallel anzubieten, ohne alte abzuschalten. FĂĽr echte Versionierung (z. B. unterschiedliche Felder im JSON) ist das aber nicht gedacht. DafĂĽr ist ein eigenes Versionierungs-Konzept besser geeignet, zum Beispiel wie in API Versioning verstehen beschrieben.
Welche Header sollten Responses setzen?
Mindestens Content-Type. Bei Sprache zusätzlich Content-Language. Und wenn sich Antworten je nach Header unterscheiden, sollte Vary gesetzt werden, damit Caches korrekt arbeiten.

