Spring Boot REST Annotations Deep Dive
This chapter dives into REST annotations, ResponseEntity for full control over responses, and content negotiation.
Mapping Annotations
@RestController
@RequestMapping("/api/books")
public class BookController {
@GetMapping // GET /api/books
@GetMapping("/{id}") // GET /api/books/1
@GetMapping("/search") // GET /api/books/search?title=Java
@PostMapping // POST /api/books
@PutMapping("/{id}") // PUT /api/books/1
@PatchMapping("/{id}") // PATCH /api/books/1
@DeleteMapping("/{id}") // DELETE /api/books/1
}
These are shortcuts for @RequestMapping(method = RequestMethod.GET).
ResponseEntity — Full Control
ResponseEntity lets you control status code, headers, and body:
@RestController
@RequestMapping("/api/books")
public class BookController {
@GetMapping("/{id}")
public ResponseEntity<Book> getById(@PathVariable Long id) {
return bookService.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<Book> create(@RequestBody Book book) {
Book created = bookService.create(book);
URI location = URI.create("/api/books/" + created.getId());
return ResponseEntity
.created(location) // 201 + Location header
.body(created);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
bookService.delete(id);
return ResponseEntity.noContent().build(); // 204
}
}
ResponseEntity Builder Methods
| Method | Status Code |
|---|---|
ResponseEntity.ok(body) |
200 |
ResponseEntity.created(uri).body(obj) |
201 |
ResponseEntity.accepted() |
202 |
ResponseEntity.noContent().build() |
204 |
ResponseEntity.badRequest().body(errors) |
400 |
ResponseEntity.notFound().build() |
404 |
ResponseEntity.status(HttpStatus.CONFLICT).body(msg) |
409 |
Custom Headers
@GetMapping("/{id}")
public ResponseEntity<Book> getById(@PathVariable Long id) {
Book book = bookService.findById(id);
return ResponseEntity.ok()
.header("X-Book-Author", book.getAuthor())
.header("Cache-Control", "max-age=3600")
.body(book);
}
Produces and Consumes
@PostMapping(
consumes = MediaType.APPLICATION_JSON_VALUE, // accepts JSON
produces = MediaType.APPLICATION_JSON_VALUE // returns JSON
)
public ResponseEntity<Book> create(@RequestBody Book book) {
return ResponseEntity.created(uri).body(bookService.create(book));
}
// Accept XML too
@GetMapping(produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
public List<Book> getAll() {
return bookService.findAll();
}
DTOs (Data Transfer Objects)
Never expose your entity directly — use DTOs:
// Request DTO
public record CreateBookRequest(
String title,
String author,
String isbn,
BigDecimal price
) {}
// Response DTO
public record BookResponse(
Long id,
String title,
String author,
String isbn,
BigDecimal price,
LocalDate publishedDate
) {
public static BookResponse from(Book book) {
return new BookResponse(
book.getId(),
book.getTitle(),
book.getAuthor(),
book.getIsbn(),
book.getPrice(),
book.getPublishedDate()
);
}
}
@PostMapping
public ResponseEntity<BookResponse> create(@RequestBody CreateBookRequest request) {
Book book = bookService.create(request);
BookResponse response = BookResponse.from(book);
URI location = URI.create("/api/books/" + book.getId());
return ResponseEntity.created(location).body(response);
}
Key Takeaways
- Use specific mapping annotations:
@GetMapping,@PostMapping, etc. ResponseEntitygives full control — status, headers, body.- Always return proper HTTP status codes (201 for created, 204 for deleted).
- Use DTOs — never expose JPA entities directly in API responses.
- Set
Locationheader on POST/201 responses.
What’s Next?
Next: Request Parameters — @PathVariable, @RequestParam, @RequestBody, pagination.