Zum Hauptinhalt springen

Woche 4, Tag 2 — Request Mapping im Detail

Ziel

Heute verstehst du Request Mapping im Detail.

Die Kernfragen:

  1. Was ist @RequestMapping?
  2. Was sind @GetMapping, @PostMapping, @PutMapping, @PatchMapping und @DeleteMapping?
  3. Wie kombinieren sich Class-Level- und Method-Level-Mappings?
  4. Was ist @PathVariable?
  5. Was ist @RequestParam?
  6. Was ist @RequestHeader?
  7. Was sind die Mapping-Conditions params und headers?
  8. Was sind consumes und produces?
  9. Was ist Content Negotiation?
  10. Welche typischen Prüfungsfallen gibt es?

1. Kurz-Wiederholung aus Woche 4, Tag 1

Am Tag 1 hast du gelernt:

  • Spring MVC mappt HTTP-Requests auf Java-Controller-Methoden.
  • DispatcherServlet ist der Front Controller.
  • HandlerMapping findet den richtigen Handler.
  • HandlerAdapter ruft den Handler auf.
  • @RestController bedeutet @Controller plus @ResponseBody.
  • @RequestBody liest den HTTP-Body.
  • @PathVariable liest Werte aus dem Pfad.
  • @RequestParam liest Query-Parameter.
  • HttpMessageConverter konvertiert HTTP-Bodies zu Java-Objekten und Java-Objekte zu HTTP-Bodies.

Merksatz:


Request -> DispatcherServlet -> HandlerMapping -> Controller -> Response

Heute gehst du tiefer: Wie Spring die richtige Controller-Methode auswählt.


2. Was ist @RequestMapping?

@RequestMapping mappt Web-Requests auf Controller-Klassen oder -Methoden.

Einfache Definition:

@RequestMapping sagt Spring MVC, welcher HTTP-Request von welcher Controller-Methode verarbeitet werden soll.

Beispiel:


@RestController
@RequestMapping("/api/tasks")
public class TaskController {

@RequestMapping("/hello")
public String hello() {
return "Hello";
}
}

Das verarbeitet:


GET /api/tasks/hello
POST /api/tasks/hello
PUT /api/tasks/hello

Warum?

Weil keine HTTP-Methode angegeben wurde.

Wichtig:

Wenn keine HTTP-Methode angegeben ist, kann @RequestMapping mehrere HTTP-Methoden matchen.


3. Besser: HTTP-Methode angeben

Statt:


@RequestMapping("/api/tasks")
public List<TaskDto> list() {
return taskService.findAll();
}

Besser:


@GetMapping("/api/tasks")
public List<TaskDto> list() {
return taskService.findAll();
}

Warum?

Weil die Methode klar nur das verarbeitet:


GET /api/tasks

— nicht jede mögliche HTTP-Methode.

Merksatz:

In REST-Controllern spezifische Mapping-Annotationen wie @GetMapping und @PostMapping bevorzugen.


4. @RequestMapping kann viele Bedingungen matchen

Ein Request Mapping kann matchen nach:


path
HTTP method
Query-Parameter
Header
consumes content type
produces content type

Beispiel:


@RequestMapping(
path = "/api/tasks",
method = RequestMethod.POST,
consumes = "application/json",
produces = "application/json"
)
public TaskDto create(@RequestBody CreateTaskRequest request) {
return taskService.create(request);
}

Das bedeutet:


Path muss /api/tasks sein.
HTTP-Methode muss POST sein.
Request-Body muss JSON sein.
Response wird JSON sein.

5. Shortcut-Mapping-Annotationen

Spring liefert Shortcut-Annotationen:


@GetMapping
@PostMapping
@PutMapping
@PatchMapping
@DeleteMapping

Sie sind Shortcuts für @RequestMapping(method = ...).

Beispiel:


@GetMapping("/api/tasks")

ist ein Shortcut für:


@RequestMapping(
path = "/api/tasks",
method = RequestMethod.GET
)

Merksatz:

Shortcut-Mappings sind für REST-APIs klarer.


6. Bedeutung der HTTP-Methode in REST

HTTP MethodTypische REST-Bedeutung
GETDaten lesen
POSTneue Resource anlegen oder Befehl ausführen
PUTResource ersetzen
PATCHResource teilweise aktualisieren
DELETEResource löschen

Beispiel:


GET /api/tasks -> Tasks auflisten
GET /api/tasks/123 -> einen Task holen
POST /api/tasks -> Task anlegen
PUT /api/tasks/123 -> Task ersetzen
PATCH /api/tasks/123 -> Teil des Tasks aktualisieren
DELETE /api/tasks/123 -> Task löschen

7. Vollständiges REST-Mapping-Beispiel


@RestController
@RequestMapping("/api/tasks")
public class TaskController {

@GetMapping
public List<TaskDto> list() {
return taskService.findAll();
}

@GetMapping("/{id}")
public TaskDto get(@PathVariable Long id) {
return taskService.findById(id);
}

@PostMapping
public ResponseEntity<TaskDto> create(@RequestBody CreateTaskRequest request) {
TaskDto created = taskService.create(request);
return ResponseEntity.status(HttpStatus.CREATED).body(created);
}

@PutMapping("/{id}")
public TaskDto replace(
@PathVariable Long id,
@RequestBody ReplaceTaskRequest request
) {
return taskService.replace(id, request);
}

@PatchMapping("/{id}")
public TaskDto update(
@PathVariable Long id,
@RequestBody UpdateTaskRequest request
) {
return taskService.update(id, request);
}

@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
taskService.delete(id);
return ResponseEntity.noContent().build();
}
}

Mappings:


GET /api/tasks -> list()
GET /api/tasks/123 -> get(123)
POST /api/tasks -> create(request)
PUT /api/tasks/123 -> replace(123, request)
PATCH /api/tasks/123 -> update(123, request)
DELETE /api/tasks/123 -> delete(123)

8. Class-Level- und Method-Level-Mapping

Class-Level-Mapping:


@RequestMapping("/api/tasks")

Method-Level-Mapping:


@GetMapping("/{id}")

Kombiniert:


GET /api/tasks/{id}

Beispiel:


@RestController
@RequestMapping("/api/clients")
public class ClientController {

@GetMapping("/{id}")
public ClientDto getClient(@PathVariable Long id) {
return clientService.findById(id);
}
}

Das verarbeitet:


GET /api/clients/10

Merksatz:

Class-Level-Pfad plus Method-Level-Pfad ergeben den finalen Endpoint-Pfad.


9. Leeres Method-Mapping

Beispiel:


@RestController
@RequestMapping("/api/tasks")
public class TaskController {

@GetMapping
public List<TaskDto> list() {
return taskService.findAll();
}
}

@GetMapping hat keinen Pfad.

Es nutzt den Class-Level-Pfad:


GET /api/tasks

Das ist üblich und sauber.


10. Volle Pfade nicht wiederholen

Weniger sauber:


@RestController
public class TaskController {

@GetMapping("/api/tasks")
public List<TaskDto> list() {
return taskService.findAll();
}

@GetMapping("/api/tasks/{id}")
public TaskDto get(@PathVariable Long id) {
return taskService.findById(id);
}
}

Sauberer:


@RestController
@RequestMapping("/api/tasks")
public class TaskController {

@GetMapping
public List<TaskDto> list() {
return taskService.findAll();
}

@GetMapping("/{id}")
public TaskDto get(@PathVariable Long id) {
return taskService.findById(id);
}
}

Merksatz:

Den gemeinsamen Basis-Pfad auf die Controller-Klasse setzen.


11. @PathVariable

@PathVariable liest einen Wert aus dem URL-Pfad.

Request:


GET /api/tasks/123

Controller:


@GetMapping("/api/tasks/{id}")
public TaskDto getTask(@PathVariable Long id) {
return taskService.findById(id);
}

Spring bindet:


123 -> id

12. Mehrere Path Variables

Request:


GET /api/clients/10/tasks/99

Controller:


@GetMapping("/api/clients/{clientId}/tasks/{taskId}")
public TaskDto getClientTask(
@PathVariable Long clientId,
@PathVariable Long taskId
) {
return taskService.findClientTask(clientId, taskId);
}

Spring bindet:


clientId = 10
taskId = 99

13. Path-Variable-Name weicht vom Parameter-Namen ab

Pfad:


@GetMapping("/api/tasks/{taskId}")

Parameter:


public TaskDto getTask(@PathVariable("taskId") Long id) {
return taskService.findById(id);
}

Spring bindet:


URL-Variable taskId -> Java-Parameter id

Nutze das, wenn der Java-Parameter-Name vom Path-Variable-Namen abweicht.


14. Typ-Konvertierung für Path Variables

Path Variables sind Text in der URL.

Beispiel:


GET /api/tasks/123

Spring empfängt:


"123"

Der Controller erwartet:


@PathVariable Long id

Spring konvertiert:


"123" -> Long 123

Wenn die Konvertierung fehlschlägt:


GET /api/tasks/abc

Spring kann nicht konvertieren:


"abc" -> Long

Typische Antwort:


400 Bad Request

Merksatz:

Path Variables sind zuerst Strings — Spring konvertiert sie dann in den Zieltyp.


15. @RequestParam

@RequestParam liest Query-Parameter.

Request:


GET /api/tasks?page=0&size=20

Controller:


@GetMapping("/api/tasks")
public List<TaskDto> list(
@RequestParam int page,
@RequestParam int size
) {
return taskService.findPage(page, size);
}

Spring bindet:


page=0 -> page
size=20 -> size

16. Pflicht-Query-Parameter

Standardmäßig ist @RequestParam required.

Beispiel:


@GetMapping("/api/tasks")
public List<TaskDto> list(@RequestParam String status) {
return taskService.findByStatus(status);
}

Request:


GET /api/tasks

Kein status-Parameter.

Typische Antwort:


400 Bad Request

Weil ein required Query-Parameter fehlt.


17. Optionale Query-Parameter

Option 1: required = false


@GetMapping("/api/tasks")
public List<TaskDto> list(
@RequestParam(required = false) String status
) {
return taskService.findByStatus(status);
}

Wenn fehlend:


status = null

Option 2: Optional


@GetMapping("/api/tasks")
public List<TaskDto> list(
@RequestParam Optional<String> status
) {
return taskService.findByStatus(status);
}

Option 3: Default-Wert


@GetMapping("/api/tasks")
public List<TaskDto> list(
@RequestParam(defaultValue = "OPEN") String status
) {
return taskService.findByStatus(status);
}

Wenn fehlend:


status = OPEN

Merksatz:

@RequestParam ist standardmäßig required — außer du machst ihn optional oder gibst einen Default-Wert an.


18. Query-Parameter-Name weicht vom Java-Parameter ab

Request:


GET /api/tasks?task_status=OPEN

Controller:


@GetMapping("/api/tasks")
public List<TaskDto> list(
@RequestParam("task_status") String status
) {
return taskService.findByStatus(status);
}

Spring bindet:


task_status -> status

19. Typ-Konvertierung bei @RequestParam

Request:


GET /api/tasks?page=0&size=20&archived=false

Controller:


@GetMapping("/api/tasks")
public List<TaskDto> list(
@RequestParam int page,
@RequestParam int size,
@RequestParam boolean archived
) {
return taskService.find(page, size, archived);
}

Spring konvertiert:


"0" -> int 0
"20" -> int 20
"false" -> boolean false

Wenn die Konvertierung fehlschlägt:


GET /api/tasks?page=abc

Typische Antwort:


400 Bad Request

20. Mehrere Werte für denselben Query-Parameter

Request:


GET /api/tasks?status=OPEN&status=DONE

Controller:


@GetMapping("/api/tasks")
public List<TaskDto> list(
@RequestParam List<String> status
) {
return taskService.findByStatuses(status);
}

Spring bindet:


status = ["OPEN", "DONE"]

Das ist nützlich für Filter.


21. @RequestHeader

@RequestHeader liest HTTP-Header.

Request:


GET /api/tasks
X-Tenant-Id: tenant-123

Controller:


@GetMapping("/api/tasks")
public List<TaskDto> list(
@RequestHeader("X-Tenant-Id") String tenantId
) {
return taskService.findForTenant(tenantId);
}

Spring bindet:


X-Tenant-Id -> tenantId

22. Optionaler Request-Header


@GetMapping("/api/tasks")
public List<TaskDto> list(
@RequestHeader(value = "X-Request-Id", required = false) String requestId
) {
return taskService.findAll(requestId);
}

Wenn der Header fehlt:


requestId = null

Mit Default-Wert:


@GetMapping("/api/tasks")
public List<TaskDto> list(
@RequestHeader(value = "X-Client-Version", defaultValue = "unknown") String clientVersion
) {
return taskService.findAll(clientVersion);
}

23. @RequestBody

@RequestBody liest den HTTP-Request-Body.

Request:


POST /api/tasks
Content-Type: application/json

{
"title": "Learn Spring MVC"
}

Controller:


@PostMapping("/api/tasks")
public TaskDto create(@RequestBody CreateTaskRequest request) {
return taskService.create(request);
}

Spring nutzt einen HttpMessageConverter, um JSON zu konvertieren in:


CreateTaskRequest

DTO:


public record CreateTaskRequest(String title) {
}

Merksatz:

@RequestBody bedeutet: Body zu Java-Objekt.


24. @RequestBody ist meist für POST, PUT, PATCH

Übliche Nutzung:


POST -> create body
PUT -> replace body
PATCH -> partial update body

Weniger üblich:


GET mit Body

In REST-APIs GET-Request-Bodies vermeiden.

Nutze Query-Parameter für Filter und Pagination.

Beispiel:


GET /api/tasks?status=OPEN&page=0&size=20

nicht:


GET /api/tasks

{
"status": "OPEN"
}

Merksatz:

Query-Parameter für GET-Filter. Request-Body für Create/Update-Befehle.


25. Ein @RequestBody pro Methode

Eine Controller-Methode hat normalerweise einen Request-Body.

Schlecht:


@PostMapping("/api/tasks")
public TaskDto create(
@RequestBody CreateTaskRequest request,
@RequestBody Metadata metadata
) {
return taskService.create(request, metadata);
}

Warum schlecht?


Der HTTP-Request hat einen Body.
Spring kann normalerweise nicht zwei separate Request-Bodies in zwei Objekte lesen.

Besser:


public record CreateTaskCommand(
String title,
Metadata metadata
) {
}

Controller:


@PostMapping("/api/tasks")
public TaskDto create(@RequestBody CreateTaskCommand command) {
return taskService.create(command);
}

Merksatz:

Ein HTTP-Request hat einen Body — also ein @RequestBody-Objekt nutzen.


26. Mapping mit Query-Parameter-Conditions: params

params im Mapping bedeutet:

Diese Methode matcht nur, wenn bestimmte Query-Parameter vorhanden sind oder bestimmte Werte haben.

Beispiel:


@GetMapping(value = "/api/tasks", params = "status")
public List<TaskDto> listByStatus(@RequestParam String status) {
return taskService.findByStatus(status);
}

Das passt zu:


GET /api/tasks?status=OPEN

But nicht:


GET /api/tasks

27. Mapping nach Parameter-Wert


@GetMapping(value = "/api/tasks", params = "status=OPEN")
public List<TaskDto> openTasks() {
return taskService.findOpenTasks();
}

Das passt zu:


GET /api/tasks?status=OPEN

But nicht:


GET /api/tasks?status=DONE

28. Mapping bei fehlendem Parameter


@GetMapping(value = "/api/tasks", params = "!status")
public List<TaskDto> listAll() {
return taskService.findAll();
}

Das passt zu:


GET /api/tasks

But nicht:


GET /api/tasks?status=OPEN

Merksatz:

params ist eine Mapping-Condition. @RequestParam liest den Wert.


29. params vs. @RequestParam

Das ist eine wichtige Prüfungsfalle.

params

Wird bei der Mapping-Auswahl genutzt.


@GetMapping(value = "/api/tasks", params = "status")

Bedeutung:


Diese Methode matcht nur, wenn der Request einen status-Parameter hat.

@RequestParam

Wird genutzt, um den Wert an einen Methoden-Parameter zu binden.


public List<TaskDto> list(@RequestParam String status)

Bedeutung:


status-Wert in Java-Parameter lesen.

Merksatz:

params wählt die Methode. @RequestParam liest den Wert.


30. Mapping mit Header-Conditions: headers

headers im Mapping bedeutet:

Diese Methode matcht nur, wenn bestimmte Header vorhanden sind oder bestimmte Werte haben.

Beispiel:


@GetMapping(value = "/api/tasks", headers = "X-API-Version=1")
public List<TaskDto> listV1() {
return taskService.findAllV1();
}

Das passt zu:


GET /api/tasks
X-API-Version: 1

But nicht:


GET /api/tasks
X-API-Version: 2

31. Bedingung: Header vorhanden


@GetMapping(value = "/api/tasks", headers = "X-Tenant-Id")
public List<TaskDto> listForTenant(
@RequestHeader("X-Tenant-Id") String tenantId
) {
return taskService.findForTenant(tenantId);
}

Diese Methode matcht nur, wenn der Request hat:


X-Tenant-Id

Header.

Merksatz:

headers wählt die Methode. @RequestHeader liest den Header-Wert.


32. headers vs. @RequestHeader

headers

Mapping-Condition:


@GetMapping(value = "/api/tasks", headers = "X-API-Version=2")

@RequestHeader

Value Binding:


public List<TaskDto> list(@RequestHeader("X-API-Version") String version)

Merksatz:

headers steuert das Matching. @RequestHeader liest Daten.


33. consumes

consumes bedeutet:

Diese Methode akzeptiert Requests mit einem bestimmten Content-Type.

Beispiel:


@PostMapping(
value = "/api/tasks",
consumes = "application/json"
)
public TaskDto create(@RequestBody CreateTaskRequest request) {
return taskService.create(request);
}

Das passt zu:


POST /api/tasks
Content-Type: application/json

But nicht:


POST /api/tasks
Content-Type: text/plain

Merksatz:

consumes prüft den Content-Type des Request-Bodies.


34. Content-Type-Header

consumes nutzt den Request-Content-Type.

Beispiel:


Content-Type: application/json

bedeutet:


Der Request-Body ist JSON.

Wenn der Controller sagt:


consumes = "application/json"

dann muss der Request einen kompatiblen JSON-Content-Type senden.


35. Falscher Content-Type

Controller:


@PostMapping(
value = "/api/tasks",
consumes = "application/json"
)
public TaskDto create(@RequestBody CreateTaskRequest request) {
return taskService.create(request);
}

Request:


POST /api/tasks
Content-Type: text/plain

hello

Typische Antwort:


415 Unsupported Media Type

Weil der Server diesen Request-Body-Media-Type für diesen Endpoint nicht unterstützt.


36. produces

produces bedeutet:

Diese Methode kann einen bestimmten Response-Media-Type erzeugen.

Beispiel:


@GetMapping(
value = "/api/tasks",
produces = "application/json"
)
public List<TaskDto> list() {
return taskService.findAll();
}

Das bedeutet: Die Methode erzeugt JSON.

Client-Request:


GET /api/tasks
Accept: application/json

Spring kann JSON zurückgeben.

Merksatz:

produces beschreibt den Content-Type der Response.


37. Accept-Header

Der Client kann senden:


Accept: application/json

Bedeutung:


Ich will eine JSON-Response.

Die Server-Methode kann deklarieren:


produces = "application/json"

Bedeutung:


Ich kann JSON erzeugen.

Wenn der Client etwas will, was der Server nicht erzeugen kann, kann die Response sein:


406 Not Acceptable

38. consumes vs. produces

Mapping-AttributPrüftBedeutung
consumesRequest-Content-Typewas der Endpoint akzeptiert
producesRequest-Accept-Header / Response-Typwas der Endpoint zurückgibt

Merksatz:


consumes = Request-Body-Format.
produces = Response-Body-Format.

39. JSON-Controller-Beispiel mit consumes und produces


@RestController
@RequestMapping("/api/tasks")
public class TaskController {

@PostMapping(
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE
)
public ResponseEntity<TaskDto> create(
@RequestBody CreateTaskRequest request
) {
TaskDto created = taskService.create(request);
return ResponseEntity.status(HttpStatus.CREATED).body(created);
}
}

Dieser Endpoint:


akzeptiert JSON-Request-Body
liefert JSON-Response-Body

40. MediaType-Konstanten

Statt Hardcoding:


"application/json"

Nutze:


MediaType.APPLICATION_JSON_VALUE

Beispiel:


@PostMapping(
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE
)

Das vermeidet Tippfehler.


41. Content Negotiation

Content Negotiation bedeutet:

Spring wählt das Response-Format basierend darauf, was der Client will und was der Server erzeugen kann.

Client:


Accept: application/json

Controller:


@GetMapping(value = "/api/tasks", produces = "application/json")

Spring liefert JSON.

Wenn mehrere Converter und Formate existieren, wählt Spring die beste Übereinstimmung.

Für normale REST-APIs:


JSON ist das häufigste Response-Format.

42. Mehrere Methoden, gleicher Pfad, unterschiedliche produces

Beispiel:


@GetMapping(value = "/api/report", produces = "application/json")
public ReportDto reportJson() {
return reportService.getReport();
}

@GetMapping(value = "/api/report", produces = "text/csv")
public String reportCsv() {
return reportService.getReportCsv();
}

Request:


GET /api/report
Accept: application/json

ruft auf:


reportJson()

Request:


GET /api/report
Accept: text/csv

ruft auf:


reportCsv()

43. Mehrdeutiges Mapping

Schlecht:


@GetMapping("/api/tasks")
public List<TaskDto> list1() {
return taskService.findAll();
}

@GetMapping("/api/tasks")
public List<TaskDto> list2() {
return taskService.findRecent();
}

Beide matchen:


GET /api/tasks

Spring kann nicht entscheiden.

Meist schlägt der Start mit einem Ambiguous-Mapping-Fehler fehl.

Fix: Mappings unterscheidbar machen:


@GetMapping("/api/tasks")
public List<TaskDto> list() {
return taskService.findAll();
}

@GetMapping("/api/tasks/recent")
public List<TaskDto> recent() {
return taskService.findRecent();
}

oder Conditions sorgfältig hinzufügen.

Merksatz:

Zwei identische Mappings führen zu Mehrdeutigkeit.


44. Pfad-Design: ressourcenorientierte URLs

Gute REST-Pfade:


/api/tasks
/api/tasks/{id}
/api/clients/{clientId}/tasks
/api/clients/{clientId}/tasks/{taskId}

Weniger gut:


/api/getTask
/api/createTask
/api/deleteTask

Warum?

Weil die HTTP-Methode die Aktion schon beschreibt.

Gut:


GET /api/tasks/1
POST /api/tasks
DELETE /api/tasks/1

Merksatz:

Substantive in URLs, HTTP-Methoden für Aktionen.


45. Query-Parameter für Filter, Sortierung, Pagination

Gut:


GET /api/tasks?status=OPEN&page=0&size=20&sort=createdAt,desc

Controller:


@GetMapping("/api/tasks")
public List<TaskDto> list(
@RequestParam(required = false) String status,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(defaultValue = "createdAt,desc") String sort
) {
return taskService.find(status, page, size, sort);
}

Nutze Query-Parameter für:


Filter
Sortierung
Pagination
Suche
optionale Einstellungen

46. Path Variables für Resource-Identität

Nutze Path Variables für Resource-Identität.

Gut:


GET /api/tasks/123

weil 123 den Task identifiziert.

Gut:


GET /api/clients/10/tasks/99

weil 10 den Client und 99 den Task identifiziert.

Merksatz:

Path Variables identifizieren Resources. Query-Parameter filtern Resources.


47. PathVariable vs. RequestParam

SituationNutze
Eine Resource identifizieren@PathVariable
Liste filtern@RequestParam
Pagination@RequestParam
Sortierung@RequestParam
Suchbegriff@RequestParam
Verschachtelte Resource-ID@PathVariable

Beispiele:


GET /api/tasks/123

Nutze:


@PathVariable Long id

GET /api/tasks?status=OPEN

Nutze:


@RequestParam String status

48. Header für Metadaten

Nutze Header für Request-Metadaten.

Beispiele:


Authorization
X-Request-Id
X-Correlation-Id
X-Tenant-Id
Accept-Language
Content-Type
Accept

Beispiel:


@GetMapping("/api/tasks")
public List<TaskDto> list(
@RequestHeader("X-Correlation-Id") String correlationId
) {
return taskService.findAll(correlationId);
}

Nutze Header nicht für normale Resource-Identität, wenn Path Variables klarer sind.


49. Häufige HTTP-Header

HeaderBedeutung
Content-TypeFormat des Request-Bodies
Acceptgewünschtes Response-Format
AuthorizationAuthentifizierungsdaten
Accept-Languagebevorzugte Sprache
X-Request-IdRequest-Tracing
X-Correlation-IdDistributed Tracing
X-Tenant-IdTenant-Kontext in Multi-Tenant-Apps

50. Praxisbeispiel für eine Klarsync-ähnliche App


@RestController
@RequestMapping("/api/clients/{clientId}/tasks")
public class ClientTaskController {

@GetMapping
public List<TaskDto> listTasks(
@PathVariable Long clientId,
@RequestParam(required = false) String status,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestHeader(value = "X-Request-Id", required = false) String requestId
) {
return taskService.findClientTasks(clientId, status, page, size, requestId);
}

@PostMapping(
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE
)
public ResponseEntity<TaskDto> createTask(
@PathVariable Long clientId,
@RequestBody CreateTaskRequest request
) {
TaskDto created = taskService.createClientTask(clientId, request);
return ResponseEntity.status(HttpStatus.CREATED).body(created);
}
}

Request:


GET /api/clients/10/tasks?status=OPEN&page=0&size=20
X-Request-Id: req-123

Bindings:


clientId = 10
status = OPEN
page = 0
size = 20
requestId = req-123

51. Häufige Response-Codes im Zusammenhang mit Mapping

SituationTypischer Status
Kein passender Pfad404 Not Found
Pfad existiert, aber falsche HTTP-Methode405 Method Not Allowed
Request-Body-Content-Type nicht unterstützt415 Unsupported Media Type
Client will nicht unterstützten Response-Typ406 Not Acceptable
Required Query-Parameter fehlt400 Bad Request
Path-Variable-Konvertierung schlägt fehl400 Bad Request
Ungültiger JSON-Body400 Bad Request

52. Typische Prüfungsfallen

Falle 1

@RequestMapping ohne Methode kann viele HTTP-Methoden matchen.

Bevorzuge @GetMapping, @PostMapping usw.


Falle 2

Class-Level- und Method-Level-Pfade kombinieren sich.


@RequestMapping("/api/tasks")
@GetMapping("/{id}")

bedeutet:


GET /api/tasks/{id}

Falle 3

@PathVariable liest aus dem URL-Pfad.

@RequestParam liest aus der Query-String.

@RequestHeader liest aus Headern.

@RequestBody liest aus dem Body.


Falle 4

params ist eine Mapping-Condition.

@RequestParam ist eine Value-Binding-Annotation.


Falle 5

headers ist eine Mapping-Condition.

@RequestHeader ist eine Value-Binding-Annotation.


Falle 6

consumes prüft den Request-Content-Type.

produces prüft, welchen Response-Typ die Methode erzeugen kann.


Falle 7

Falscher Content-Type kann 415 Unsupported Media Type auslösen.

Nicht unterstütztes Accept kann 406 Not Acceptable auslösen.


Falle 8

Zwei identische Mappings können Ambiguous-Mapping-Fehler auslösen.


Falle 9

@RequestParam ist standardmäßig required.


Falle 10

Nutze Query-Parameter für Filter und Pagination.

Nutze Path Variables für Resource-Identität.


53. Prüfungsfrage: @RequestMapping

Frage:

Was macht @RequestMapping?

Antwort:

@RequestMapping mappt HTTP-Requests auf Controller-Klassen oder -Methoden. Es kann nach Pfad, HTTP-Methode, Request-Parametern, Headern, consumed media type und produced media type matchen.


54. Prüfungsfrage: Shortcut-Annotationen

Frage:

Was sind @GetMapping und @PostMapping?

Antwort:

Sie sind zusammengesetzte Shortcut-Annotationen für @RequestMapping mit einer bestimmten HTTP-Methode. @GetMapping mappt GET-Requests, @PostMapping mappt POST-Requests.


55. Prüfungsfrage: Class-Level-Mapping

Frage:

Wie lautet der finale Pfad?


@RestController
@RequestMapping("/api/tasks")
public class TaskController {

@GetMapping("/{id}")
public TaskDto get(@PathVariable Long id) {
return taskService.findById(id);
}
}

Antwort:


GET /api/tasks/{id}

56. Prüfungsfrage: @PathVariable

Frage:

Was macht @PathVariable?

Antwort:

@PathVariable bindet einen Wert aus dem URL-Pfad an einen Controller-Methoden-Parameter.


57. Prüfungsfrage: @RequestParam

Frage:

Was macht @RequestParam?

Antwort:

@RequestParam bindet einen Query-Parameter aus der Request-URL an einen Controller-Methoden-Parameter.


58. Prüfungsfrage: Required Query-Parameter

Frage:

Was passiert, wenn ein required @RequestParam fehlt?

Antwort:

Spring liefert meist 400 Bad Request.


59. Prüfungsfrage: @RequestHeader

Frage:

Was macht @RequestHeader?

Antwort:

@RequestHeader bindet einen HTTP-Request-Header-Wert an einen Controller-Methoden-Parameter.


60. Prüfungsfrage: params

Frage:

Was bedeutet das?


@GetMapping(value = "/api/tasks", params = "status")

Antwort:

Diese Methode matcht nur Requests auf /api/tasks, die einen status-Query-Parameter enthalten.


61. Prüfungsfrage: headers

Frage:

Was bedeutet das?


@GetMapping(value = "/api/tasks", headers = "X-API-Version=2")

Antwort:

Diese Methode matcht nur Requests mit Header X-API-Version gleich 2.


62. Prüfungsfrage: consumes

Frage:

Was bedeutet consumes = "application/json"?

Antwort:

Das bedeutet: Der Endpoint akzeptiert Requests, deren Body Content-Type: application/json hat.


63. Prüfungsfrage: produces

Frage:

Was bedeutet produces = "application/json"?

Antwort:

Das bedeutet: Der Endpoint kann eine JSON-Response erzeugen.


64. Prüfungsfrage: consumes vs. produces

Frage:

Was ist der Unterschied zwischen consumes und produces?

Antwort:

consumes beschreibt, welchen Request-Body-Media-Type der Endpoint akzeptiert — basierend auf Content-Type. produces beschreibt, welchen Response-Media-Type der Endpoint zurückgeben kann — basierend auf Content Negotiation und dem Accept-Header.


65. Prüfungsfrage: Mehrdeutiges Mapping

Frage:

Was passiert, wenn zwei Controller-Methoden exakt dasselbe Mapping haben?

Antwort:

Spring startet meist nicht und wirft einen Ambiguous-Mapping-Fehler, weil es nicht entscheiden kann, welche Methode den Request verarbeiten soll.


66. Interview-Antwort

Frage:

Erkläre Request Mapping in Spring MVC.

Gute Antwort:

Request Mapping ist die Art, wie Spring MVC HTTP-Requests auf Controller-Methoden mappt. Ein Mapping kann nach Pfad, HTTP-Methode, Query-Parametern, Headern, Request-Content-Type über consumes und Response-Content-Type über produces matchen. In REST-Controllern nutze ich meist Shortcut-Annotationen wie @GetMapping, @PostMapping, @PutMapping, @PatchMapping und @DeleteMapping.


67. Interview-Antwort

Frage:

Was ist der Unterschied zwischen @PathVariable, @RequestParam, @RequestHeader und @RequestBody?

Gute Antwort:

@PathVariable liest Werte aus dem URL-Pfad, z. B. /tasks/{id}. @RequestParam liest Query-Parameter, z. B. /tasks?page=0. @RequestHeader liest HTTP-Header, z. B. X-Request-Id. @RequestBody liest den HTTP-Request-Body und konvertiert ihn in ein Java-Objekt — meist aus JSON.


68. Interview-Antwort

Frage:

Was ist der Unterschied zwischen params und @RequestParam?

Gute Antwort:

params ist eine Mapping-Condition. Es steuert, ob eine Controller-Methode zu einem Request passt. Zum Beispiel bedeutet params = "status", dass die Methode nur matcht, wenn der Request einen status-Query-Parameter hat. @RequestParam wird genutzt, nachdem die Methode gewählt wurde, um den Query-Parameter-Wert an einen Java-Methoden-Parameter zu binden.


69. Interview-Antwort

Frage:

Was ist der Unterschied zwischen consumes und produces?

Gute Antwort:

consumes beschreibt, welchen Request-Body-Media-Type der Endpoint akzeptiert — meist basierend auf dem Content-Type-Header. Zum Beispiel bedeutet consumes = "application/json", dass der Request-Body JSON sein muss. produces beschreibt, welchen Response-Media-Type der Endpoint zurückgeben kann — meist basierend auf dem Accept-Header und Content Negotiation. Zum Beispiel bedeutet produces = "application/json", dass der Endpoint JSON zurückgeben kann.


70. Interview-Antwort

Frage:

Wie designst du REST-Pfade?

Gute Antwort:

Ich nutze ressourcenorientierte Pfade mit Substantiven, nicht mit Aktionsverben. Zum Beispiel GET /api/tasks/123 statt /api/getTask. Die HTTP-Methode beschreibt die Aktion. Path Variables nutze ich für Resource-Identität, z. B. Task-ID. Query-Parameter für Filter, Sortierung und Pagination.


71. Kleine Code-Übung

Erstelle diesen Controller:


@RestController
@RequestMapping("/api/clients/{clientId}/tasks")
public class ClientTaskController {

@GetMapping
public List<TaskDto> list(
@PathVariable Long clientId,
@RequestParam(required = false) String status,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestHeader(value = "X-Request-Id", required = false) String requestId
) {
return taskService.find(clientId, status, page, size, requestId);
}

@PostMapping(
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE
)
public ResponseEntity<TaskDto> create(
@PathVariable Long clientId,
@RequestBody CreateTaskRequest request
) {
TaskDto created = taskService.create(clientId, request);
return ResponseEntity.status(HttpStatus.CREATED).body(created);
}
}

Request:


GET /api/clients/10/tasks?status=OPEN&page=1&size=5
X-Request-Id: req-999

Fragen:

  1. Welche Methode verarbeitet den Request?
  2. Was ist clientId?
  3. Was ist status?
  4. Was ist page?
  5. Was ist size?
  6. Was ist requestId?

Antworten:

  1. list
  2. 10
  3. OPEN
  4. 1
  5. 5
  6. req-999

72. Kleine Bug-Übung 1

Problem:


@GetMapping("/api/tasks")
public List<TaskDto> list1() {
return taskService.findAll();
}

@GetMapping("/api/tasks")
public List<TaskDto> list2() {
return taskService.findRecent();
}

Frage:

Was ist falsch?

Antwort:

Beide Methoden haben dasselbe Mapping. Spring kann nicht entscheiden, welche Methode GET /api/tasks verarbeiten soll — die Anwendung kann mit einem Ambiguous-Mapping-Fehler fehlschlagen.


73. Kleine Bug-Übung 2

Problem:


@PostMapping(
value = "/api/tasks",
consumes = MediaType.APPLICATION_JSON_VALUE
)
public TaskDto create(@RequestBody CreateTaskRequest request) {
return taskService.create(request);
}

Request:


POST /api/tasks
Content-Type: text/plain

hello

Frage:

Was passiert?

Antwort:

Der Endpoint consumes JSON, aber der Request sendet text/plain. Spring liefert meist 415 Unsupported Media Type.


74. Kleine Bug-Übung 3

Problem:


@GetMapping("/api/tasks")
public List<TaskDto> list(@RequestParam String status) {
return taskService.findByStatus(status);
}

Request:


GET /api/tasks

Frage:

Was passiert?

Antwort:

status ist standardmäßig required. Weil es fehlt, liefert Spring meist 400 Bad Request.

Fix:


@RequestParam(required = false) String status

oder:


@RequestParam(defaultValue = "OPEN") String status

Übungsfragen

Frage 1

Was macht @RequestMapping?

Antwort:

@RequestMapping mappt HTTP-Requests auf Controller-Klassen oder -Methoden. Es kann nach Pfad, HTTP-Methode, Request-Parametern, Headern, consumed media type und produced media type matchen.


Frage 2

Warum solltest du @GetMapping statt @RequestMapping für GET-Endpoints bevorzugen?

Antwort:

@GetMapping mappt klar nur GET-Requests. @RequestMapping ohne Methode kann mehrere HTTP-Methoden matchen — weniger präzise für REST-Endpoints.


Frage 3

Nenne fünf Shortcut-Mapping-Annotationen.

Antwort:

Fünf Shortcut-Mapping-Annotationen sind:


@GetMapping
@PostMapping
@PutMapping
@PatchMapping
@DeleteMapping

Frage 4

Wie kombinieren sich Class-Level- und Method-Level-Mappings?

Antwort:

Class-Level-Pfad und Method-Level-Pfad kombinieren sich zum finalen Endpoint-Pfad. Zum Beispiel wird @RequestMapping("/api/tasks") plus @GetMapping("/{id}") zu GET /api/tasks/{id}.


Frage 5

Was liest @PathVariable?

Antwort:

@PathVariable liest Werte aus dem URL-Pfad.


Frage 6

Was liest @RequestParam?

Antwort:

@RequestParam liest Query-Parameter aus der URL.


Frage 7

Ist @RequestParam standardmäßig required?

Antwort:

Ja. @RequestParam ist standardmäßig required.


Frage 8

Wie kannst du einen Request-Parameter optional machen?

Antwort:

Nutze required = false, Optional<T> oder defaultValue.


Frage 9

Was liest @RequestHeader?

Antwort:

@RequestHeader liest HTTP-Request-Header.


Frage 10

Was liest @RequestBody?

Antwort:

@RequestBody liest den HTTP-Request-Body und konvertiert ihn in ein Java-Objekt.


Frage 11

Kann eine Methode normalerweise zwei @RequestBody-Parameter haben?

Antwort:

Meist nein. Ein HTTP-Request hat einen Body — eine Methode sollte normalerweise ein @RequestBody-Objekt haben.


Frage 12

Was bedeutet params = "status"?

Antwort:

params = "status" bedeutet: Die Methode matcht nur, wenn der Request einen status-Query-Parameter hat.


Frage 13

Was ist der Unterschied zwischen params und @RequestParam?

Antwort:

params ist eine Mapping-Condition, die bei der Wahl der Controller-Methode hilft. @RequestParam bindet den Query-Parameter-Wert an einen Java-Methoden-Parameter.


Frage 14

Was bedeutet headers = "X-API-Version=2"?

Antwort:

Das bedeutet: Die Methode matcht nur, wenn der Request Header X-API-Version mit Wert 2 hat.


Frage 15

Was ist der Unterschied zwischen headers und @RequestHeader?

Antwort:

headers ist eine Mapping-Condition, die bei der Methodenwahl hilft. @RequestHeader bindet einen Header-Wert an einen Java-Methoden-Parameter.


Frage 16

Was bedeutet consumes?

Antwort:

consumes beschreibt, welchen Request-Body-Media-Type der Endpoint akzeptiert — basierend auf dem Content-Type-Header.


Frage 17

Was bedeutet produces?

Antwort:

produces beschreibt, welchen Response-Media-Type der Endpoint zurückgeben kann — basierend auf Content Negotiation und dem Accept-Header.


Frage 18

Was ist der Unterschied zwischen Content-Type und Accept?

Antwort:

Content-Type beschreibt das Format des Request-Bodies. Accept beschreibt das Response-Format, das der Client will.


Frage 19

Was kann 415 Unsupported Media Type auslösen?

Antwort:

415 Unsupported Media Type kann passieren, wenn der Request-Content-Type von der consumes-Condition des Endpoints nicht unterstützt wird.


Frage 20

Was kann zu einem Ambiguous Mapping führen?

Antwort:

Ambiguous Mapping passiert, wenn zwei oder mehr Controller-Methoden denselben Request matchen und Spring nicht entscheiden kann, welche es nutzen soll.

Merksätze zum Mitnehmen

  • @RequestMapping mappt HTTP-Requests auf Controller-Methoden.
  • Bevorzuge @GetMapping, @PostMapping, @PutMapping, @PatchMapping und @DeleteMapping für REST-APIs.
  • Class-Level- und Method-Level-Mappings kombinieren sich.
  • @PathVariable liest aus dem URL-Pfad.
  • @RequestParam liest aus der Query-String.
  • @RequestHeader liest aus Request-Headern.
  • @RequestBody liest aus dem HTTP-Body.
  • @RequestParam ist standardmäßig required.
  • params ist eine Mapping-Condition.
  • @RequestParam bindet einen Wert.
  • headers ist eine Mapping-Condition.
  • @RequestHeader bindet einen Wert.
  • consumes prüft den Request-Content-Type.
  • produces beschreibt den Response-Media-Type.
  • Content-Type beschreibt das Request-Body-Format.
  • Accept beschreibt das gewünschte Response-Format.
  • Falscher Content-Type kann 415 auslösen.
  • Nicht unterstütztes Accept kann 406 auslösen.
  • Identische Mappings können Ambiguous-Mapping-Fehler auslösen.
  • Nutze Path Variables für Resource-Identität.
  • Query-Parameter für Filter, Sortierung und Pagination nutzen.