Cheat SheetsSpring BootWeb / REST

Web / REST — Cheat Sheet

Spring Boot · 10 topics. Download the PDF or the Instagram carousel and share it.

Cheat Sheet · AiCanCode.org
Web / REST
Spring Boot10 topicsQuick revision reference
1

@RestController & @Controller

@RestController combines @Controller and @ResponseBody, turning every handler method into a JSON/XML endpoint without needing explicit serialisation annotations.

  • @RestController = @Controller + @ResponseBody on every method.
  • Return values are serialised by HttpMessageConverters (Jackson for JSON by default).
  • @Controller returns view names; add @ResponseBody on individual methods for JSON.
  • Use ResponseEntity<T> for fine-grained control over status codes and headers.
  • @RestControllerAdvice centralises error handling across all controllers.
  • Content negotiation is driven by the Accept header — @RestController supports both JSON and XML with the right dependency.
Java — @RestController example
@RestController
@RequestMapping("/api/products")
public class ProductController {

    private final ProductService productService;

    public ProductController(ProductService productService) {
        this.productService = productService;
    }

    @GetMapping
    public List<ProductDTO> listAll() {
        return productService.findAll();  // serialised to JSON array
    }

    @GetMapping("/{id}")
    public ResponseEntity<ProductDTO> getById(@PathVariable Long id) {
        return productService.findById(id)
            .map(ResponseEntity::ok)
            .orElse(ResponseEntity.notFound().build());
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public ProductDTO create(@RequestBody @Valid CreateProductRequest req) {
        return productService.create(req);
    }
}
2

Request Mapping & HTTP Methods

@GetMapping, @PostMapping, @PutMapping, @DeleteMapping, and @PatchMapping are shortcuts for @RequestMapping(method=…) that map HTTP verbs to handler methods.

  • @GetMapping, @PostMapping, etc. are composed annotations for cleaner code than @RequestMapping(method=...).
  • Class-level @RequestMapping sets a base path; method-level annotations append sub-paths.
  • consumes narrows by Content-Type; produces narrows by Accept header.
  • Path variables use {name} templates; regex constraints use {name:regex}.
  • Ant wildcards: * matches one segment, ** matches multiple segments.
  • Matrix variables (;key=val) need enableMatrixVariables = true in WebMvcConfigurer.
Java — HTTP verb shortcut annotations
@RestController
@RequestMapping("/api/orders")   // base path for all methods below
public class OrderController {

    @GetMapping                          // GET  /api/orders
    public List<OrderDTO> list() { ... }

    @GetMapping("/{id}")                 // GET  /api/orders/{id}
    public OrderDTO get(@PathVariable Long id) { ... }

    @PostMapping                         // POST /api/orders
    @ResponseStatus(HttpStatus.CREATED)
    public OrderDTO create(@RequestBody @Valid CreateOrderRequest req) { ... }

    @PutMapping("/{id}")                 // PUT  /api/orders/{id}
    public OrderDTO replace(@PathVariable Long id, @RequestBody OrderDTO dto) { ... }

    @PatchMapping("/{id}/status")        // PATCH /api/orders/{id}/status
    public OrderDTO updateStatus(@PathVariable Long id,
                                 @RequestBody StatusRequest req) { ... }

    @DeleteMapping("/{id}")              // DELETE /api/orders/{id}
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void delete(@PathVariable Long id) { ... }
}
3

@PathVariable & @RequestParam

@PathVariable extracts template variables from the URI path; @RequestParam binds query-string parameters, both support type conversion and defaulting.

  • @PathVariable binds {template} segments; name defaults to parameter name, override with value().
  • @RequestParam is required=true by default; use required=false or defaultValue for optional params.
  • Both support automatic type conversion: String → int, Long, enum, LocalDate, etc.
  • List<T> on @RequestParam captures repeated query params (?tags=a&tags=b).
  • Map<String, String> on @RequestParam captures all query params dynamically.
  • Add @Validated on the controller class to enable JSR-303 validation on @RequestParam values.
Java — @PathVariable examples
@RestController
@RequestMapping("/api/orders")
public class OrderController {

    // Simple path variable
    @GetMapping("/{id}")
    public OrderDTO getById(@PathVariable Long id) {
        return orderService.findById(id);
    }

    // Multiple path variables
    @GetMapping("/{year}/{month}")
    public List<OrderDTO> getByMonth(
            @PathVariable int year,
            @PathVariable int month) {
        return orderService.findByMonth(year, month);
    }

    // Rename if variable name differs from template
    @GetMapping("/{order-id}/items")
    public List<ItemDTO> getItems(
            @PathVariable("order-id") Long orderId) {  // hyphen not valid Java identifier
        return orderService.findItems(orderId);
    }

    // Regex constraint — only digits
    @GetMapping("/{id:[0-9]+}/invoice")
    public InvoiceDTO getInvoice(@PathVariable Long id) {
        return orderService.getInvoice(id);
    }
}
4

@RequestBody & @ResponseBody

@RequestBody deserialises the HTTP body into a Java object using Jackson; @ResponseBody serialises the return value back to JSON or XML.

  • @RequestBody deserialises the HTTP body via HttpMessageConverter (Jackson for JSON).
  • @ResponseBody writes the return value directly to the HTTP response — implicit in @RestController.
  • Add @Valid to @RequestBody to trigger Bean Validation; MethodArgumentNotValidException on failure.
  • spring.jackson.deserialization.fail-on-unknown-properties=false prevents errors on extra fields.
  • Use JavaTimeModule to serialise/deserialise java.time types (LocalDateTime, ZonedDateTime).
  • Return StreamingResponseBody or Flux for large or real-time responses without memory buffering.
Java — @RequestBody with @Valid and error handling
// Request DTO with validation constraints
public record CreateOrderRequest(
    @NotNull Long customerId,
    @NotEmpty List<@Valid OrderLineRequest> lines,
    @Size(max = 500) String notes
) {}

public record OrderLineRequest(
    @NotBlank String sku,
    @Min(1) @Max(100) int quantity
) {}

// Controller
@RestController
@RequestMapping("/api/orders")
public class OrderController {

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public OrderDTO create(@RequestBody @Valid CreateOrderRequest req) {
        // If any @NotNull / @NotEmpty / @Min fails, Spring throws
        // MethodArgumentNotValidException before this method body runs
        return orderService.create(req);
    }
}

// Handle validation errors globally
@RestControllerAdvice
public class ValidationHandler {
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Map<String, String> handleValidation(MethodArgumentNotValidException ex) {
        return ex.getBindingResult().getFieldErrors().stream()
            .collect(Collectors.toMap(
                FieldError::getField,
                FieldError::getDefaultMessage
            ));
    }
}
5

ResponseEntity

Wraps the response body, HTTP status code, and headers in a single return type, giving full control over every aspect of the HTTP response.

  • ResponseEntity.ok(body) = 200, .created(uri) = 201 + Location, .noContent() = 204, .notFound() = 404
  • Always return 201 Created with a Location header pointing to the new resource URI after POST
  • ResponseEntity.status(code).header(name, value).body(obj) provides full control over every response aspect
  • @ControllerAdvice + @ExceptionHandler centralises error-to-ResponseEntity mapping across all controllers
  • ProblemDetail (Spring Boot 3 / RFC 7807) provides a standardised JSON error structure with type, title, status, detail
  • Return ResponseEntity<Void> for 204 No Content — avoids Jackson trying to serialise a null body
Java — GET (200/404), POST (201 + Location), DELETE (204) patterns
@RestController
@RequestMapping("/orders")
public class OrderController {

    private final OrderService orderService;

    // 200 OK with body
    @GetMapping("/{id}")
    public ResponseEntity<Order> getOrder(@PathVariable Long id) {
        return orderService.findById(id)
            .map(ResponseEntity::ok)               // 200 OK
            .orElse(ResponseEntity.notFound().build()); // 404 Not Found, no body
    }

    // 201 Created with Location header
    @PostMapping
    public ResponseEntity<Order> createOrder(@RequestBody @Valid OrderRequest req) {
        Order created = orderService.create(req);
        URI location = ServletUriComponentsBuilder
            .fromCurrentRequest()
            .path("/{id}")
            .buildAndExpand(created.getId())
            .toUri();
        return ResponseEntity.created(location).body(created);
        // Response: 201 Created, Location: /orders/42, body: {"id":42,...}
    }

    // 204 No Content — update/delete returns nothing
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteOrder(@PathVariable Long id) {
        orderService.delete(id);
        return ResponseEntity.noContent().build();  // 204, no body
    }
}
6

Exception Handling — @ControllerAdvice

@ControllerAdvice combined with @ExceptionHandler centralises error handling across all controllers, returning consistent error response DTOs.

  • @RestControllerAdvice centralises all exception-to-response mapping in one class, eliminating try-catch in every controller.
  • Return a consistent error DTO (status, error, message, timestamp) so all API consumers can parse errors uniformly.
  • MethodArgumentNotValidException carries per-field validation errors — extract and return field-level messages in a 400 response.
  • Define a custom exception hierarchy in the service layer; never reference HttpStatus there — keep HTTP concerns in the advice class.
  • Always have a catch-all @ExceptionHandler(Exception.class) as the last resort to prevent stack traces leaking to clients.
  • @ResponseStatus on the method is convenient; use ResponseEntity<ApiError> when you need to set response headers dynamically.
Java — Spring Boot
// Consistent error response DTO
@Getter
@Builder
public class ApiError {
    private int     status;
    private String  error;
    private String  message;
    private Instant timestamp;
}

// Global exception handler
@RestControllerAdvice
public class GlobalExceptionHandler {

    // 404 — Resource not found
    @ExceptionHandler(EntityNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ApiError handleNotFound(EntityNotFoundException ex) {
        return ApiError.builder()
            .status(404)
            .error("Not Found")
            .message(ex.getMessage())
            .timestamp(Instant.now())
            .build();
    }

    // 409 — Business rule violation
    @ExceptionHandler(DuplicateResourceException.class)
    @ResponseStatus(HttpStatus.CONFLICT)
    public ApiError handleConflict(DuplicateResourceException ex) {
        return ApiError.builder()
            .status(409).error("Conflict").message(ex.getMessage())
            .timestamp(Instant.now()).build();
    }

    // 500 — Catch-all for unexpected errors
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ApiError handleAll(Exception ex) {
        log.error("Unhandled exception", ex);
        return ApiError.builder()
            .status(500).error("Internal Server Error")
            .message("An unexpected error occurred")
            .timestamp(Instant.now()).build();
    }
}
7

Bean Validation with @Valid

JSR-303/380 constraints (@NotNull, @Size, @Email) on request DTOs are enforced automatically when @Valid is placed on the method parameter.

  • Add spring-boot-starter-validation to get Hibernate Validator on the classpath
  • @Valid on @RequestBody triggers validation; @Validated adds group support
  • @NotBlank is stricter than @NotNull — it also rejects empty/whitespace strings
  • Nested objects require their own @Valid annotation for cascaded validation
  • Handle MethodArgumentNotValidException in @ControllerAdvice for structured 400 responses
  • Custom constraints: create a @Constraint annotation + ConstraintValidator<A, T> class
Spring Boot — constraint annotations on request DTO
public record CreateUserRequest(
    @NotBlank(message = "Name is required")
    @Size(max = 100, message = "Name must be at most 100 characters")
    String name,

    @NotNull @Email(message = "Must be a valid email address")
    String email,

    @NotNull @Min(18) @Max(120)
    Integer age,

    @Valid                          // cascade validation into the nested object
    @NotNull
    AddressRequest address
) {}

public record AddressRequest(
    @NotBlank String street,
    @Pattern(regexp = "^[0-9]{5}$", message = "ZIP must be 5 digits")
    String zip
) {}
8

CORS Configuration

Cross-Origin Resource Sharing is configured globally via WebMvcConfigurer or per-controller with @CrossOrigin to control which origins browsers allow.

  • CORS is enforced by browsers, not servers — server-to-server calls are never blocked by CORS.
  • Spring Security must be involved in CORS config; WebMvcConfigurer alone is bypassed when security is active.
  • Never use allowedOrigins("*") with allowCredentials(true) — browsers reject it and it is a security vulnerability.
  • Preflight OPTIONS requests must return 200 quickly; Spring handles them automatically when CORS is configured.
  • Use allowedOrigins with explicit domains in production; use environment-specific properties to avoid hardcoding.
  • exposedHeaders lists headers the browser JavaScript can read from the response — only listed headers are accessible.
Java — global CORS via WebMvcConfigurer
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
            .allowedOrigins(
                "https://app.example.com",
                "https://staging.example.com"
            )
            .allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")
            .allowedHeaders("*")
            .exposedHeaders("X-Correlation-Id", "Location")
            .allowCredentials(true)  // allows cookies/Authorization headers
            .maxAge(3600);           // cache preflight for 1 hour

        // Public endpoints — allow all origins, no credentials
        registry.addMapping("/public/**")
            .allowedOrigins("*")
            .allowedMethods("GET")
            .allowCredentials(false);
    }
}
9

Filters & Interceptors

Servlet Filters operate at the servlet container level for all requests; Spring HandlerInterceptors run around MVC dispatch and have access to handler metadata.

  • Filters run at the servlet container level — before Spring MVC. Interceptors run inside DispatcherServlet — after handler resolution.
  • Interceptors have access to HandlerMethod — you can read method annotations and controller class for context-aware logic.
  • Spring Security uses Filters (not Interceptors) because security must run before MVC handler resolution.
  • Register filters with @Component (all paths) or FilterRegistrationBean (specific patterns + order control).
  • Register interceptors with WebMvcConfigurer.addInterceptors() and scope them with addPathPatterns().
  • Filters cannot access Spring beans directly unless using DelegatingFilterProxy; Interceptors are Spring-managed and can @Autowire anything.
Java — Servlet Filter
// Custom filter — logs request/response timing
@Component
@Order(1)   // lower number = higher priority
public class RequestLoggingFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {
        HttpServletRequest  req  = (HttpServletRequest)  request;
        HttpServletResponse resp = (HttpServletResponse) response;

        long start = System.currentTimeMillis();
        String requestId = UUID.randomUUID().toString().substring(0, 8);
        req.setAttribute("requestId", requestId);

        try {
            chain.doFilter(request, response);  // pass to next filter / servlet
        } finally {
            long duration = System.currentTimeMillis() - start;
            log.info("[{}] {} {} → {} ({}ms)",
                requestId, req.getMethod(), req.getRequestURI(),
                resp.getStatus(), duration);
        }
    }
}

// Fine-grained registration — apply only to /api/**
@Bean
public FilterRegistrationBean<RequestLoggingFilter> loggingFilter() {
    FilterRegistrationBean<RequestLoggingFilter> reg = new FilterRegistrationBean<>();
    reg.setFilter(new RequestLoggingFilter());
    reg.addUrlPatterns("/api/*");
    reg.setOrder(1);
    return reg;
}
10

Content Negotiation

Spring MVC selects the response format (JSON, XML, etc.) based on Accept headers or URL suffixes, driven by registered HttpMessageConverters.

  • Spring picks the HttpMessageConverter based on the Accept header (response) or Content-Type header (request body)
  • produces = MediaType.APPLICATION_JSON_VALUE on a method restricts the endpoint to JSON output only — returns 406 otherwise
  • consumes = MediaType.APPLICATION_JSON_VALUE restricts accepted request body type — returns 415 for other types
  • JAXB2 XML support requires @XmlRootElement on the entity and jackson-dataformat-xml or JAXB2 on the classpath
  • @JsonView selects field subsets per endpoint, avoiding separate DTO classes for summary vs detail views
  • Custom HttpMessageConverters can support any media type (CSV, protobuf, YAML) by extending AbstractHttpMessageConverter
Java — produces/consumes annotations and Accept header negotiation
// Client sends: Accept: application/xml
// Spring finds Jaxb2 converter → returns XML

@RestController
@RequestMapping("/orders")
public class OrderController {

    // Endpoint produces both JSON and XML — client chooses via Accept header
    @GetMapping(value = "/{id}",
                produces = {MediaType.APPLICATION_JSON_VALUE,
                            MediaType.APPLICATION_XML_VALUE})
    public Order getOrder(@PathVariable Long id) {
        return orderService.findById(id).orElseThrow();
        // GET /orders/1  Accept: application/json  → {"id":1,...}
        // GET /orders/1  Accept: application/xml   → <Order><id>1</id>...</Order>
    }

    // Restrict endpoint to only accept JSON request body
    @PostMapping(value = "/",
                 consumes = MediaType.APPLICATION_JSON_VALUE,
                 produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<Order> createOrder(@RequestBody OrderRequest req) {
        // Will return 415 Unsupported Media Type if client sends XML
        return ResponseEntity.ok(orderService.create(req));
    }
}

// XML support requires JAXB annotations on the entity
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Order { ... }
Learn this free with Aria, your AI tutor → AiCanCode.org/learn/spring-boot