Spring Study Guide – Web Layer

August 12, 20267 min readUpdated 8/18/2026

Spring MVC is one servlet and a set of delegates behind it. Once you can name the delegates and say what each one does with a request, most of the questions answer themselves.

The pattern

What does MVC stand for and what is the idea?

Model–View–Controller. Three responsibilities kept apart so each can change without the others:

  • Model — the data the view needs.
  • View — how it is rendered.
  • Controller — handles the request, decides what to do, chooses the view.

The payoff is substitution: the same controller can render HTML or JSON, and the controller can be tested without either.

Is Spring MVC part of spring-core?

No. It is its own module, spring-webmvc, brought in by spring-boot-starter-webmvc.

The DispatcherServlet

What is it and what is it for?

The front controller: a single servlet that receives every request in the application and coordinates the components that handle it. Centralising the entry point is what allows one place to own routing, argument binding, view resolution and exception handling.

Walk through a request.

  1. The DispatcherServlet receives it.
  2. HandlerMapping finds the handler — usually a controller method.
  3. Any HandlerInterceptors run their preHandle.
  4. HandlerAdapter invokes the method, resolving each argument with an HandlerMethodArgumentResolver.
  5. The return value is handled: either a ModelAndView, or — with @ResponseBody — written straight to the response by an HttpMessageConverter.
  6. ViewResolver turns a view name into a View.
  7. The View renders using the model.
  8. Anything thrown along the way goes to a HandlerExceptionResolver.

The delegates worth naming: HandlerMapping, HandlerAdapter, HandlerExceptionResolver, ViewResolver, LocaleResolver, MultipartResolver.

Is the DispatcherServlet instantiated via an application context?

Yes. In a classic setup there are two contexts: a root context holding services and repositories, and a servlet (web) context per DispatcherServlet holding controllers, view resolvers and handler mappings. The servlet context is a child of the root — so controllers can see services, but services cannot see controllers.

In Spring Boot there is normally just one context and the distinction rarely comes up. It is still asked about, and it still explains why a bean defined in the wrong place cannot be seen.

What is a web application context and what extra scopes does it have?

A WebApplicationContext — an ApplicationContext aware of the ServletContext. It adds request, session, application and websocket scopes on top of singleton and prototype.

What is the default scope in the web context? Still singleton — which is why a controller must not hold per-request state in a field.

What is the session scope for? State that must survive across requests from the same user — a wizard's partial input, for instance. Note it makes the application stateful, so it does not survive a load balancer without sticky sessions or a shared session store. The pizza API avoids it entirely and is STATELESS by design.

Controllers

What is @Controller for?

It marks the class as a web handler and as a @Component, so component scanning finds it and the handler mapping will consider its methods.

How is a request mapped to a method?

By @RequestMapping and its shortcuts, matched on path, HTTP method, and optionally headers, params, consumes and produces. A class-level mapping is the prefix; method-level mappings are appended to it.

@RestController
@RequestMapping("/api/products")
public class ProductRestController {

    @GetMapping
    public ResponseEntity<List<ProductDTO>> getProducts(
            @RequestParam(required = false) ProductType type) {
        List<ProductDTO> products =
                type == null ? productService.getMenu() : productService.getByType(type);
        return new ResponseEntity<>(products, OK);
    }

    @GetMapping("/{id}")
    public ResponseEntity<ProductDTO> getProduct(@PathVariable UUID id) {
        return new ResponseEntity<>(productService.getProductByPublicId(id), OK);
    }
}

What is the difference between @RequestMapping and @GetMapping?

@GetMapping is @RequestMapping(method = GET) as a meta-annotation. There is one per verb — @PostMapping, @PutMapping, @PatchMapping, @DeleteMapping. Use them; a bare @RequestMapping matches every verb, which is almost never intended.

Method parameters

What is @RequestParam for, and how does it differ from @PathVariable?

  • @RequestParam reads a query-string or form parameter — ?type=PIZZA.
  • @PathVariable reads a segment of the URI itself — /api/products/{id}.

Both are required by default. @RequestParam(required = false) or a defaultValue makes one optional. Spring converts the string to the declared type, so UUID and enum parameters simply work — and a bad value becomes a MethodArgumentTypeMismatchException, which you should handle as a 400 rather than let become a 500.

What parameter types can a controller method declare?

HttpServletRequest / HttpServletResponse, HttpSession, Model / ModelMap, Principal, Locale, HttpEntity, BindingResult, MultipartFile, a command object to bind onto, and any of your own types Spring can convert to.

Which annotations can go on a parameter?

AnnotationBinds from
@RequestParamquery string or form field
@PathVariablea URI template variable
@RequestBodythe request body, deserialised
@RequestHeadera header
@CookieValuea cookie
@ModelAttributethe model, or bound from the request
@SessionAttributethe session
@Valid— triggers bean validation on the bound object

What return types are valid?

String (a view name), ModelAndView, View, void (the method wrote the response itself), ResponseEntity<T>, any object with @ResponseBody, Callable or DeferredResult for async, and HttpEntity.

Model and view

What is the Model? A map of named attributes passed from the controller to the view. It is the contract between them: everything the template dereferences must be put in it, and nothing else is visible.

// @Controller, NOT @RestController. That single difference is the whole lesson:
// @RestController is @Controller + @ResponseBody, and @ResponseBody would send the
// literal text "receipt" to the browser instead of resolving it to a template.
@Controller
public class OrderReceiptController {

    @GetMapping("/orders/{id}/receipt")
    public String receipt(@PathVariable UUID id, Model model) {
        OrderDTO order = orderService.getOrderByPublicId(id);
        model.addAttribute("order", order);

        // Resolved to templates/receipt.html by the Thymeleaf view resolver.
        return "receipt";
    }
}

Put a DTO in the model, never an entity. Rendering happens after the controller returns, by which time open-in-view=false has closed the persistence context — so a lazy association touched by the template throws LazyInitializationException in the view layer, which is a miserable place to debug it.

What is a View, and why support different types?

A View renders the model into the response. Supporting several — Thymeleaf, JSON, PDF, Excel — means the same controller and the same model can produce different outputs, chosen by content negotiation rather than by branching in the controller.

What does a ViewResolver do, and how is the view chosen?

It maps the logical view name the controller returned onto a concrete View. Resolvers are consulted in order until one returns non-null. The usual implementation prepends a prefix and appends a suffix:

# Thymeleaf's defaults - "receipt" becomes classpath:/templates/receipt.html
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html

# Boot's default is true. Off in development, or an edit to a template appears to do
# nothing until the app restarts.
spring.thymeleaf.cache=false

Return "redirect:/orders" for a redirect and "forward:/other" for a server-side forward — those prefixes are handled before any resolver sees the name.

Why controllers are testable

Why are controllers testable artifacts?

Because a controller method is an ordinary method on an ordinary bean. It takes typed parameters and returns a value; it does not extend a framework class or require a HttpServletRequest. So you can call it directly with plain arguments — or drive the whole MVC stack without a server using MockMvc:

@SpringBootTest
@AutoConfigureMockMvc
@Transactional
class ApiSecurityIntegrationTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void menuIsPublic() throws Exception {
        mockMvc.perform(get("/api/products")).andExpect(status().isOk());
    }
}

That is real routing, real argument binding, real converters and real security filters — with no port bound and no container started.

Configuring MVC

How do you customise Spring MVC without losing Boot's defaults?

Implement WebMvcConfigurer and override only what you need. Adding @EnableWebMvc switches Boot's MVC auto-configuration off entirely — almost never what you want in a Boot application.

@Configuration
public class RestMVCConfig {

    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {

            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**")
                        .allowCredentials(true)
                        .allowedHeaders("*")
                        .allowedMethods("*")
                        .allowedOrigins("http://localhost:5173", "http://127.0.0.1:5173");
            }

            @Override
            public void addResourceHandlers(ResourceHandlerRegistry registry) {
                registry.addResourceHandler("/webjars/**")
                        .addResourceLocations("classpath:/META-INF/resources/webjars/");
            }
        };
    }
}

Other hooks on the same interface: addInterceptors, addArgumentResolvers, addFormatters, configureMessageConverters.

When to render on the server at all

Server-side rendering earns its place when the output is a document: it has to be printable, it has to work from a link in an email where no JavaScript app boots, and the same template can render the email body. It does not earn its place for a menu or a cart, which are interactive and belong in the browser app.

What to remember

  • One front controller — the DispatcherServlet — delegating to HandlerMapping, HandlerAdapter, ViewResolver, HandlerExceptionResolver.
  • Controllers are singletons, so no mutable state in fields.
  • @RequestParam = query string, @PathVariable = URI segment.
  • @RestController = @Controller + @ResponseBody; using it on a view controller sends the view name as text.
  • Customise with WebMvcConfigurer; never add @EnableWebMvc in Boot.