Woche 4, Tag 2 — Request Mapping im Detail
Ziel
Heute verstehst du Request Mapping im Detail.
Die Kernfragen:
- Was ist
@RequestMapping? - Was sind
@GetMapping,@PostMapping,@PutMapping,@PatchMappingund@DeleteMapping? - Wie kombinieren sich Class-Level- und Method-Level-Mappings?
- Was ist
@PathVariable? - Was ist
@RequestParam? - Was ist
@RequestHeader? - Was sind die Mapping-Conditions
paramsundheaders? - Was sind
consumesundproduces? - Was ist Content Negotiation?
- 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.
DispatcherServletist der Front Controller.HandlerMappingfindet den richtigen Handler.HandlerAdapterruft den Handler auf.@RestControllerbedeutet@Controllerplus@ResponseBody.@RequestBodyliest den HTTP-Body.@PathVariableliest Werte aus dem Pfad.@RequestParamliest Query-Parameter.HttpMessageConverterkonvertiert 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:
@RequestMappingsagt 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
@RequestMappingmehrere 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
@GetMappingund@PostMappingbevorzugen.
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 Method | Typische REST-Bedeutung |
|---|---|
GET | Daten lesen |
POST | neue Resource anlegen oder Befehl ausführen |
PUT | Resource ersetzen |
PATCH | Resource teilweise aktualisieren |
DELETE | Resource 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:
@RequestParamist 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:
@RequestBodybedeutet: 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:
paramsist eine Mapping-Condition.@RequestParamliest 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:
paramswählt die Methode.@RequestParamliest 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:
headerswählt die Methode.@RequestHeaderliest 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:
headerssteuert das Matching.@RequestHeaderliest 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:
consumesprü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:
producesbeschreibt 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-Attribut | Prüft | Bedeutung |
|---|---|---|
consumes | Request-Content-Type | was der Endpoint akzeptiert |
produces | Request-Accept-Header / Response-Typ | was 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
| Situation | Nutze |
|---|---|
| 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
| Header | Bedeutung |
|---|---|
Content-Type | Format des Request-Bodies |
Accept | gewünschtes Response-Format |
Authorization | Authentifizierungsdaten |
Accept-Language | bevorzugte Sprache |
X-Request-Id | Request-Tracing |
X-Correlation-Id | Distributed Tracing |
X-Tenant-Id | Tenant-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
| Situation | Typischer Status |
|---|---|
| Kein passender Pfad | 404 Not Found |
| Pfad existiert, aber falsche HTTP-Methode | 405 Method Not Allowed |
| Request-Body-Content-Type nicht unterstützt | 415 Unsupported Media Type |
| Client will nicht unterstützten Response-Typ | 406 Not Acceptable |
| Required Query-Parameter fehlt | 400 Bad Request |
| Path-Variable-Konvertierung schlägt fehl | 400 Bad Request |
| Ungültiger JSON-Body | 400 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,@RequestHeaderund@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
paramsund@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
consumesundproduces?
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:
- Welche Methode verarbeitet den Request?
- Was ist
clientId? - Was ist
status? - Was ist
page? - Was ist
size? - Was ist
requestId?
Antworten:
list10OPEN15req-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
@RequestMappingmappt HTTP-Requests auf Controller-Methoden.- Bevorzuge
@GetMapping,@PostMapping,@PutMapping,@PatchMappingund@DeleteMappingfür REST-APIs. - Class-Level- und Method-Level-Mappings kombinieren sich.
@PathVariableliest aus dem URL-Pfad.@RequestParamliest aus der Query-String.@RequestHeaderliest aus Request-Headern.@RequestBodyliest aus dem HTTP-Body.@RequestParamist standardmäßig required.paramsist eine Mapping-Condition.@RequestParambindet einen Wert.headersist eine Mapping-Condition.@RequestHeaderbindet einen Wert.consumesprüft den Request-Content-Type.producesbeschreibt den Response-Media-Type.Content-Typebeschreibt das Request-Body-Format.Acceptbeschreibt das gewünschte Response-Format.- Falscher
Content-Typekann 415 auslösen. - Nicht unterstütztes
Acceptkann 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.