How a Request Reaches Your Controller

The Request Flow

Spring MVC is built around one central servlet, the DispatcherServlet (the "front controller"). Every request goes through it.

curl GET /api/products
        |
        v
 Embedded Tomcat (port 8080)  -- reads the HTTP text, makes a request object
        |
        v
 DispatcherServlet            -- the front controller
        |   1. HandlerMapping: which method handles GET /api/products ?
        v
 ProductController.all()     -- your code runs, returns List<Product>
        |
        v
 HttpMessageConverter (Jackson) -- List<Product>  ->  JSON text
        |
        v
 Response: 200 OK, Content-Type: application/json, body [...]

A Mini Dispatcher in Plain Java

Example

The core of handler mapping is a lookup from "method + path" to a handler function. A tiny version, including the 404 and 405 cases Spring returns:

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;

public class Main {
    static final Map<String, Supplier<Object>> handlers = new LinkedHashMap<>();
    static final List<String> catalogue = List.of("Boat Rockerz 450", "Clean Code");

    static String dispatch(String method, String path) {
        Supplier<Object> handler = handlers.get(method + " " + path);
        if (handler != null) return "200 OK          " + handler.get();
        boolean pathExists = handlers.keySet().stream().anyMatch(k -> k.endsWith(" " + path));
        return pathExists ? "405 Method Not Allowed" : "404 Not Found";
    }

    public static void main(String[] args) {
        // what @GetMapping registers at startup
        handlers.put("GET /api/products", () -> catalogue);
        handlers.put("GET /api/products/count", () -> catalogue.size());
        handlers.put("GET /ping", () -> Map.of("status", "UP"));

        System.out.println("GET    /api/products       -> " + dispatch("GET", "/api/products"));
        System.out.println("GET    /api/products/count -> " + dispatch("GET", "/api/products/count"));
        System.out.println("GET    /ping               -> " + dispatch("GET", "/ping"));
        System.out.println("DELETE /api/products       -> " + dispatch("DELETE", "/api/products"));
        System.out.println("GET    /api/orders         -> " + dispatch("GET", "/api/orders"));
    }
}

Output

GET    /api/products       -> 200 OK          [Boat Rockerz 450, Clean Code]
GET    /api/products/count -> 200 OK          2
GET    /ping               -> 200 OK          {status=UP}
DELETE /api/products       -> 405 Method Not Allowed
GET    /api/orders         -> 404 Not Found

Mappings in the Log

Set logging.level.org.springframework.web=DEBUG to see which handler each request goes to. Very useful when you get an unexpected 404.

Example

curl http://localhost:8080/api/products

Example output

... DEBUG ... o.s.web.servlet.DispatcherServlet : GET "/api/products", parameters={}
... DEBUG ... s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.shopease.product.ProductController#all()
... DEBUG ... m.m.a.RequestResponseBodyMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, ...]
... DEBUG ... o.s.web.servlet.DispatcherServlet : Completed 200 OK