REST Controllers

Mapping requests

@RestController
@RequestMapping("/api/books")
public class BookController {

    @GetMapping                 List<Book> all()                  {  }
    @GetMapping("/{id}")        Book one(@PathVariable long id)    {  }
    @PostMapping                Book create(@RequestBody Book b)   {  }
    @PutMapping("/{id}")        Book replace(@PathVariable long id, @RequestBody Book b) {  }
    @DeleteMapping("/{id}")     void delete(@PathVariable long id) {  }
}

@RestController = @Controller + @ResponseBody: return values are serialized, not resolved as view names.

Reading the request

AnnotationSourceExample
@PathVariableURL segment/books/7
@RequestParamquery string?page=2&size=20
@RequestBodyJSON bodyPOST payload
@RequestHeaderheaderAuthorization
@CookieValuecookiesession id
@GetMapping("/search")
List<Book> search(@RequestParam String q,
                  @RequestParam(defaultValue = "0") int page,
                  @RequestParam Optional<String> author) {  }

Records as DTOs

public record BookRequest(String title, String author, int year) {}
public record BookResponse(long id, String title, String author) {}

Keep web types separate from database entities — otherwise every column change is an API change.

Status codes and headers

@PostMapping
ResponseEntity<BookResponse> create(@RequestBody BookRequest req) {
    var saved = service.create(req);
    return ResponseEntity
            .created(URI.create("/api/books/" + saved.id()))   // 201 + Location
            .body(saved);
}

Or declare it:

@PostMapping
@ResponseStatus(HttpStatus.CREATED)
BookResponse create(@RequestBody BookRequest req) {  }
SituationStatus
read ok200
created201
deleted, no body204
invalid input400
not authenticated / not allowed401 / 403
unknown id404
conflict, e.g. duplicate409

Content negotiation

@GetMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE)

Jackson 3 handles JSON. Java time types serialize as ISO-8601 by default.

CORS

@CrossOrigin(origins = "https://app.example.com")
@RestController
class BookController {  }

Globally:

@Bean
WebMvcConfigurer cors() {
    return new WebMvcConfigurer() {
        @Override public void addCorsMappings(CorsRegistry reg) {
            reg.addMapping("/api/**").allowedOrigins("https://app.example.com");
        }
    };
}

MVC or WebFlux?

Spring MVCSpring WebFlux
modelone thread per requestevent loop, reactive
returnBook, List<Book>Mono<Book>, Flux<Book>
use whenthe normal casestreaming, very high concurrency

With virtual threads (spring.threads.virtual.enabled=true) blocking MVC code scales far enough for most services — start there.

Tip

Return ResponseEntity only when you need to control status or headers. Otherwise return the payload and keep the signature readable.

★ Exercises

  1. Build /api/books with an in-memory List and the five CRUD methods.
  2. Return 201 with a Location header on create.
  3. Add ?author= filtering with @RequestParam.
  4. Return 404 for an unknown id (ResponseEntity.notFound().build()).
  5. Add a second endpoint returning the same book as plain text.