Backend engineer path · A request's journey · Topic 6 of 18

From socket to controller and back

Not started

A Spring Boot web application is a plain Java program that starts an embedded server, by default Tomcat on port 8080. Tomcat accepts the TCP connection, parses the HTTP request and hands it to a worker thread, which carries it through the servlet filters and into Spring MVC's DispatcherServlet. From there Spring finds the right controller method, builds its arguments, calls it and turns the return value back into bytes, all on that same thread.

Why it matters: When a request is slow, rejected with 415 or answered with the wrong JSON, knowing each hop tells you where to look.

  1. 1Tomcat owns the thread

    The embedded server accepts the connection and runs the whole request on one worker thread from its pool (200 by default).

  2. 2One servlet in front

    Every request passes the filters and then the DispatcherServlet, which finds the handler for the path and method.

  3. 3Arguments are resolved

    Resolvers turn path variables, query parameters, headers and the body into your method's parameters.

  4. 4Converters do the JSON

    HttpMessageConverters read the request body and write the return value, chosen by Content-Type and Accept.

1

How it works

The explanation, with examples you can run.

The journey of one request

@RestController@RequestMapping("/orders")class OrderController {  private final OrderService orders;  OrderController(OrderService orders) { this.orders = orders; }   @PostMapping("/{customerId}")  ResponseEntity<OrderView> create(@PathVariable long customerId,                                   @RequestBody @Valid NewOrder body) {    OrderView created = orders.create(customerId, body);    return ResponseEntity.status(201).body(created);  }}

For POST /orders/7 with a JSON body, roughly this happens:

  1. Tomcat takes a thread from its pool (server.tomcat.threads.max, 200 by default) and builds an HttpServletRequest.
  2. Filters run in order: character encoding, Spring Security, any Filter beans of your own.
  3. The DispatcherServlet asks each HandlerMapping for a handler. RequestMappingHandlerMapping matches the path pattern and the HTTP method to create.
  4. Interceptors registered for that path get preHandle.
  5. A HandlerAdapter invokes the method. First, HandlerMethodArgumentResolvers build each argument: @PathVariable from the URI template, @RequestParam from the query string, @RequestBody by asking the HttpMessageConverter that supports the request's Content-Type (Jackson, for application/json) to read the body. @Valid then runs Bean Validation.
  6. Your method runs.
  7. The return value is written by a converter that matches the client's Accept header. For @ResponseBody and ResponseEntity this happens inside the adapter, before postHandle.
  8. If anything threw, HandlerExceptionResolvers (including your @ControllerAdvice with @ExceptionHandler methods) turn the exception into a response.

Status codes that tell you which step failed

You seeThe request stopped at
404No handler mapping matched the path
405The path matched, the HTTP method did not
415No converter can read the request's Content-Type
406No converter can write what Accept asks for
400An argument could not be bound or failed @Valid

Threads are the budget

One request holds one Tomcat thread from start to finish, including every database call and HTTP call it makes. When the pool is exhausted, new connections queue in the accept backlog (server.tomcat.accept-count, 100 by default) and are then refused. Slow downstream calls without timeouts are the usual cause. On Java 21, spring.threads.virtual.enabled=true (Boot 3.2+) runs requests on virtual threads instead, so blocking no longer pins a scarce platform thread; but the database still has only as many connections as the pool allows.

2

Check yourself

3 questions. Get them all right to finish this topic.

  1. 1.A client POSTs JSON without a Content-Type header to an @RequestBody endpoint. What is the likely response?
  2. 2.All 200 Tomcat worker threads are blocked waiting on a slow downstream service. What happens to new requests?
  3. 3.Which component maps GET /orders/42 to OrderController.get(Long id)?

0 of 3 right so far. Get all three to finish this topic.