Why this matters
- Mapping annotations are the daily vocabulary of BookStore API development — knowing the full toolkit avoids workarounds and bugs.
- Incorrect binding (missing
@RequestBody, wrong@PathVariablename) causes silent 400 errors or null parameters. - HTTP semantics (status codes, content types) belong in the controller layer, not services.
Core mapping annotations
@RestController
@RequestMapping("/api/books")
public class BookController {
private final BookService bookService;
public BookController(BookService bookService) {
this.bookService = bookService;
}
@GetMapping
public List<BookResponse> list(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
return bookService.findAll(page, size);
}
@GetMapping("/{id}")
public BookResponse get(@PathVariable Long id) {
return BookResponse.from(bookService.findById(id));
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public BookResponse create(@RequestBody CreateBookRequest request) {
return BookResponse.from(bookService.create(request));
}
@PutMapping("/{id}")
public BookResponse update(@PathVariable Long id,
@RequestBody UpdateBookRequest request) {
return BookResponse.from(bookService.update(id, request));
}
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void delete(@PathVariable Long id) {
bookService.delete(id);
}
}
Binding annotations
@PathVariable— URI segment:/api/books/{id}→Long id.@RequestParam— Query string:?genre=fiction→String genre.@RequestBody— Deserialise JSON body to a Java object.@RequestHeader— Read an HTTP header value.@ResponseStatus— Set HTTP status without returningResponseEntity.
ResponseEntity for full control
When you need custom headers or conditional status codes:
@GetMapping("/{id}")
public ResponseEntity<BookResponse> get(@PathVariable Long id) {
return bookService.findById(id)
.map(book -> ResponseEntity.ok(BookResponse.from(book)))
.orElse(ResponseEntity.notFound().build());
}
ResponseEntity wraps body, status, and headers in one return type.
Content negotiation
@GetMapping(value = "/{id}/export", produces = MediaType.APPLICATION_XML_VALUE)
public BookXml export(@PathVariable Long id) {
return bookService.toXml(id);
}
produces and consumes filter by Content-Type and Accept headers.
Path variable patterns
@GetMapping("/genre/{genre}/books")
public List<BookResponse> byGenre(@PathVariable String genre) { ... }
@GetMapping("/search/{query:.+}")
public List<BookResponse> search(@PathVariable String query) { ... }
The {query:.+} regex allows dots in the path segment — useful for ISBN lookups like /search/978-0-13-235088-4.
Request body with validation
@PostMapping
public BookResponse create(@RequestBody @Valid CreateBookRequest request) {
return BookResponse.from(bookService.create(request));
}
@Valid triggers Bean Validation on the request DTO. Invalid input returns 400 before the service method runs.
HTTP method semantics
| Method | Idempotent | Safe | BookStore use |
|---|---|---|---|
| GET | Yes | Yes | List, retrieve |
| POST | No | No | Create |
| PUT | Yes | No | Full update |
| PATCH | No | No | Partial update |
| DELETE | Yes | No | Remove |
GET
IdempotentYesSafeYesBookStore useList, retrievePOST
IdempotentNoSafeNoBookStore useCreatePUT
IdempotentYesSafeNoBookStore useFull updatePATCH
IdempotentNoSafeNoBookStore usePartial updateDELETE
IdempotentYesSafeNoBookStore useRemove
Use the correct verb — GET for reads, POST for creates, PUT for replacements, DELETE for removal.
Class-level vs method-level mapping
@RestController
@RequestMapping("/api/v1/books") // applies to all methods
public class BookController {
@GetMapping("/{id}") // resolves to /api/v1/books/{id}
public BookResponse get(@PathVariable Long id) { ... }
}
Version in the class-level path (/api/v1/) enables future /api/v2/ without breaking existing clients.
CORS preflight
Browsers send OPTIONS before cross-origin requests. Spring MVC handles this when CORS is configured — covered in the security module.
Quick recall
Everything you need if you only revisit this box.
@RestController=@Controller+@ResponseBodyfor JSON APIs.@PathVariablebinds URI segments;@RequestParambinds query parameters.@RequestBodydeserialises JSON; always pair with@Validon input DTOs.ResponseEntityprovides full control over status, headers, and body.- Use correct HTTP verbs: GET reads, POST creates, PUT replaces, DELETE removes.
- Class-level
@RequestMappingsets the base path for all handler methods.
Test yourself
Answer these before moving on — recall is what makes it stick.