> ## Documentation Index
> Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Spring MVC & REST

> Building powerful RESTful APIs

# Spring MVC & REST

Spring MVC is the web framework built on the Servlet API. In modern microservices, we mostly use it to build **REST APIs**.

**Real-world analogy:** Think of Spring MVC like a well-organized restaurant. The `DispatcherServlet` is the host who greets every customer (HTTP request) at the door. The `HandlerMapping` is the seating chart that decides which waiter (controller method) handles which table. The waiter takes the order, the kitchen (service layer) prepares the food, and the response goes back through the same chain. Filters are the bouncers at the entrance who check IDs before anyone even gets to the host. Interceptors are the managers walking the floor, observing interactions between the host and the waiters.

## 1. REST Controller Annotations

| Annotation                      | Purpose                                                 |
| :------------------------------ | :------------------------------------------------------ |
| `@RestController`               | Combines `@Controller` and `@ResponseBody`.             |
| `@RequestMapping`               | Base path for the controller (e.g., `/api/v1/users`).   |
| `@GetMapping`, `@PostMapping`   | Shortcuts for specific HTTP methods.                    |
| `@PutMapping`, `@DeleteMapping` | Update and Delete mappings.                             |
| `@PathVariable`                 | Extract values from the URI path (e.g., `/users/{id}`). |
| `@RequestParam`                 | Extract query parameters (e.g., `/users?role=admin`).   |
| `@RequestBody`                  | Map the JSON body to a Java Object (POJO).              |
| `@ResponseStatus`               | Set the HTTP status code (e.g., 201 CREATED).           |

## 2. Building a User API

Let's build a CRUD API for a `User` resource.

**The Domain Model (DTO)**

```java theme={null}
public record UserDto(Long id, String name, String email) {}
```

**The Controller**

```java theme={null}
@RestController
// Versioned API path -- always version your public APIs from day one.
// Changing /api/users to /api/v1/users later breaks every client.
@RequestMapping("/api/v1/users")
public class UserController {

    // In-memory list for demo purposes only.
    // In production, this would be a service layer backed by a repository.
    private final List<UserDto> users = new ArrayList<>();

    @GetMapping
    public List<UserDto> getAllUsers() {
        // In production, you would add pagination here (see Spring Data's Pageable).
        // Returning an unbounded list is a memory bomb waiting to go off.
        return users;
    }

    @GetMapping("/{id}")
    public UserDto getUserById(@PathVariable Long id) {
        return users.stream()
                .filter(u -> u.id().equals(id))
                .findFirst()
                // Never throw generic RuntimeException in production.
                // Use a custom ResourceNotFoundException (see @ControllerAdvice below).
                .orElseThrow(() -> new RuntimeException("User not found"));
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED) // Returns 201 instead of default 200
    public UserDto createUser(@RequestBody UserDto user) {
        users.add(user);
        // In production, return a ResponseEntity with a Location header:
        // ResponseEntity.created(URI.create("/api/v1/users/" + user.id())).body(user)
        return user;
    }
}
```

## 3. Exception Handling with @ControllerAdvice

Don't let raw stack traces leak to the client. Use global exception handling.

```java theme={null}
// @RestControllerAdvice = @ControllerAdvice + @ResponseBody.
// This class acts as a centralized exception handler for ALL controllers.
// Think of it as a safety net stretched across your entire API surface.
@RestControllerAdvice
public class GlobalExceptionHandler {

    // Catch ResourceNotFoundException and return 404 with a clean JSON body.
    // In production, create specific exception classes instead of catching RuntimeException.
    @ExceptionHandler(RuntimeException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ErrorResponse handleNotFound(RuntimeException e) {
        return new ErrorResponse(404, e.getMessage());
    }

    // Production pattern: handle validation errors separately for structured field-level errors.
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Map<String, String> handleValidationErrors(MethodArgumentNotValidException ex) {
        // Collect every field error into a map: {"email": "Invalid email format", ...}
        return ex.getBindingResult().getFieldErrors().stream()
                .collect(Collectors.toMap(
                        FieldError::getField,
                        FieldError::getDefaultMessage,
                        (first, second) -> first // Keep first error if field has multiple
                ));
    }
}

record ErrorResponse(int status, String message) {}
```

**Production tip:** In enterprise APIs, standardize your error response format across all services using [RFC 7807 Problem Details](https://datatracker.ietf.org/doc/html/rfc7807). Spring 6+ has built-in support via `ProblemDetail`. This gives clients a predictable contract for error handling regardless of which microservice they are talking to.

## 4. Bean Validation

Never trust client input. Use **Hibernate Validator** (implementation of Jakarta Bean Validation).

Add dependency `spring-boot-starter-validation`.

**Add Constraints to DTO**

```java theme={null}
public record UserCreateRequest(
    @NotBlank(message = "Name is required")
    String name,

    @Email(message = "Invalid email format")
    @NotBlank
    String email
) {}
```

**Validate in Controller**

```java theme={null}
@PostMapping
public UserDto createUser(@Valid @RequestBody UserCreateRequest request) { 
    // @Valid triggers Jakarta Bean Validation before the method body executes.
    // If validation fails, Spring throws MethodArgumentNotValidException
    // BEFORE your code runs -- you never see invalid data.
    // ... logic
}
```

You can then catch `MethodArgumentNotValidException` in your `@RestControllerAdvice` to return a nice list of validation errors (shown above).

**Pitfall -- @Valid vs @Validated:** `@Valid` is Jakarta standard and works on method parameters and nested objects. `@Validated` is Spring-specific and adds support for validation groups (e.g., validate different fields for create vs. update). Use `@Valid` by default; reach for `@Validated` only when you need group-based validation.

## 5. Content Negotiation

Spring Boot uses `Jackson` by default to serialize/deserialize Java Objects to JSON.

* If you want XML, add `jackson-dataformat-xml` dependency.
* Spring will check the `Accept` header of the request to decide whether to return JSON or XML.

**Production tip:** Configure Jackson globally rather than relying on defaults. Most enterprise APIs need these settings:

```java theme={null}
// In application.yml or a @Configuration class
@Configuration
public class JacksonConfig {
    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() {
        return builder -> builder
                // Fail on unknown fields instead of silently ignoring them.
                // Catches client typos like "naem" instead of "name".
                .featuresToEnable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
                // ISO-8601 dates instead of timestamps (1713000000 -> "2025-04-13T12:00:00Z")
                .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
                // Do not include null fields in JSON output -- smaller payloads
                .serializationInclusion(JsonInclude.Include.NON_NULL);
    }
}
```

## 6. Internal Request Lifecycle (DispatcherServlet)

Spring MVC is designed around the **Front Controller** pattern. The `DispatcherServlet` handles all incoming requests.

<div className="flex justify-center my-4">
  <div className="w-full max-w-3xl">
    ```mermaid theme={null}
    sequenceDiagram
        participant Client
        participant Tomcat
        participant DispatcherServlet
        participant HandlerMapping
        participant Controller
        participant ViewResolver

        Client->>Tomcat: HTTP GET /users/1
        Tomcat->>DispatcherServlet: Forward Request
        DispatcherServlet->>HandlerMapping: "Who handles /users/1?"
        HandlerMapping-->>DispatcherServlet: UserController.getUserById()
        DispatcherServlet->>Controller: Execute Method
        Controller-->>DispatcherServlet: Return UserDto (JSON)
        DispatcherServlet-->>Client: HTTP 200 OK (JSON)
    ```
  </div>
</div>

## 7. Filters vs Interceptors vs AOP

Interviewers love this question.

| Feature      | Filter                                   | Interceptor                                        | AOP                             |
| :----------- | :--------------------------------------- | :------------------------------------------------- | :------------------------------ |
| **Layer**    | Servlet Container (Tomcat)               | Spring MVC Framework                               | Spring Bean (Method Level)      |
| **Scope**    | Runs for ALL requests (even non-Spring)  | Runs only for valid DispatcherServlet requests     | Runs for method calls           |
| **Access**   | Raw `ServletRequest` / `ServletResponse` | `HandlerMethod` (Knows which controller is mapped) | Method Arguments & Return Value |
| **Use Case** | Security, GZip Compression, CORS         | Auth Checks, Logging execution time                | Transaction mgmt, Audit Logging |

### Implementing an Interceptor

```java theme={null}
public class LoggingInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // preHandle fires BEFORE the controller method executes.
        // Returning false short-circuits the entire request -- the controller never runs.
        // Use this for auth checks, request throttling, or tenant resolution.
        System.out.println("Incoming request: " + request.getRequestURI());
        return true; // Continue processing
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
                                 Object handler, Exception ex) {
        // afterCompletion fires AFTER the response is written, even if an exception occurred.
        // Ideal for cleanup: MDC context, request-scoped resources, timing metrics.
        if (ex != null) {
            System.out.println("Request failed: " + ex.getMessage());
        }
    }
}

// Register it -- you can scope interceptors to specific URL patterns.
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoggingInterceptor())
                .addPathPatterns("/api/**")         // Only intercept API paths
                .excludePathPatterns("/api/health"); // Skip health checks
    }
}
```

## 8. Asynchronous Requests

If an API takes 10 seconds, you don't want to block a Tomcat thread (default \~200 threads) for 10s. Once all threads are blocked, your entire server stops accepting new requests -- even fast health-check endpoints go dark.

**Analogy:** Imagine a bank with 200 teller windows. If a complex transaction takes 10 minutes per customer, all windows fill up and the line out the door grows forever. Async processing lets the teller say "I have started your wire transfer; step aside and we will call your number when it is done," freeing the window for the next customer.

**CompletableFuture**

```java theme={null}
@GetMapping("/async")
public CompletableFuture<String> asyncEndpoint() {
    return CompletableFuture.supplyAsync(() -> {
        // Runs in a separate ForkJoinPool thread, NOT a Tomcat thread.
        // The Tomcat thread is released immediately to serve other requests.
        slowService.process();
        return "Done";
    });
}
```

*Tomcat thread is released immediately. The response is sent when the future completes.*

**Production pitfall:** The default `ForkJoinPool.commonPool()` is shared across the entire JVM. If your async tasks are slow, they can starve unrelated code that also uses the common pool (including parallel streams). In production, provide a dedicated `Executor`:

```java theme={null}
@Bean("asyncTaskExecutor")
public Executor asyncExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(10);
    executor.setMaxPoolSize(50);
    executor.setQueueCapacity(100);
    executor.setThreadNamePrefix("async-");
    // Rejection policy: run in the caller thread rather than dropping the task
    executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
    executor.initialize();
    return executor;
}
```

## 9. Spring Security Integration

Spring Security is simply a chain of standard Servlet Filters.

<div className="flex justify-center my-4">
  <div className="w-full max-w-3xl">
    ```mermaid theme={null}
    graph LR
        Request --> F1[CorsFilter]
        F1 --> F2[CsrfFilter]
        F2 --> F3[AuthenticationFilter]
        F3 --> F4[AuthorizationFilter]
        F4 --> Controller
    ```
  </div>
</div>

If `AuthenticationFilter` fails (e.g., bad token), it throws an exception and the request **never** reaches the Controller.

## 10. Deep Dive: Spring Security Architecture

Spring Security is a lot more than just a few annotations.

### The Big Picture

1. **DelegatingFilterProxy:** A standard Servlet Filter (registered with Tomcat) that delegates to a Spring Bean.
2. **FilterChainProxy:** The Spring Bean that holds all security logic. It contains a list of **SecurityFilterChains**.
3. **SecurityFilterChain:** A chain of filters matching a specific URL pattern.

### The Authentication Flow

<div className="flex justify-center my-4">
  <div className="w-full max-w-3xl">
    ```mermaid theme={null}
    sequenceDiagram
        participant Client
        participant AuthFilter as AuthenticationFilter
        participant AuthMgr as AuthenticationManager
        participant Provider as AuthenticationProvider
        participant UserSvc as UserDetailsService

        Client->>AuthFilter: POST /login (user, pass)
        AuthFilter->>AuthMgr: authenticate(token)
        AuthMgr->>Provider: authenticate(token)
        Provider->>UserSvc: loadUserByUsername("user")
        UserSvc-->>Provider: UserDetails (DB Hash)
        Provider->>Provider: Check Password (Bcrypt)
        Provider-->>AuthMgr: Success (Authentication Object)
        AuthMgr-->>AuthFilter: Success
        AuthFilter-->>Client: 200 OK + JWT
    ```
  </div>
</div>

### Key Components

* **AuthenticationManager:** The API that defines how Spring Security's Filters perform authentication.
* **ProviderManager:** The standard implementation of `AuthenticationManager`. It delegates to a list of `AuthenticationProvider`s.
* **AuthenticationProvider:** Doing the actual work (e.g., `DaoAuthenticationProvider` talks to DB, `LdapAuthenticationProvider` talks to LDAP).
* **UserDetailsService:** Interface to load user-specific data using a username.

**Production pitfall -- Security filter ordering matters:** Spring Security filters execute in a strict order defined by `FilterOrderRegistration`. If you add a custom filter, use `addFilterBefore()` or `addFilterAfter()` with a reference filter, not `addFilter()`. Incorrect ordering is the number one cause of "my authentication works in tests but fails in production" bugs.

**A senior engineer would note:** In a microservices architecture, you typically authenticate at the API Gateway (verify the JWT) and then propagate the user identity downstream via a trusted header (e.g., `X-User-Id`). Internal services behind the gateway skip JWT validation entirely and trust the header. This avoids every service needing access to the JWT signing key and eliminates redundant token parsing across the call chain.

***

## Interview Deep-Dive

<AccordionGroup>
  <Accordion title="Explain the full request lifecycle in Spring MVC -- from the moment an HTTP request hits Tomcat to the moment the JSON response leaves. Where do Filters, Interceptors, and ExceptionHandlers fit in?">
    **Strong Answer:**

    * The request first hits the Servlet container (Tomcat). Before it reaches any Spring code, it passes through the Servlet Filter chain. This is where `DelegatingFilterProxy` lives, which is how Spring Security plugs into the Servlet layer. Filters see the raw `HttpServletRequest` and can reject, modify, or wrap it. Critically, filters run for every request -- even requests to static resources that Spring MVC does not handle.
    * After filters, the request reaches the `DispatcherServlet` -- Spring MVC's front controller. It is a single Servlet registered with Tomcat that handles all Spring MVC requests. The DispatcherServlet first consults `HandlerMapping` implementations to find which controller method matches the URL and HTTP method. `RequestMappingHandlerMapping` is the one that reads `@GetMapping`, `@PostMapping`, etc.
    * Before the controller executes, `HandlerInterceptor.preHandle()` runs. Interceptors have access to the `HandlerMethod` object -- they know which controller and method will be called, unlike filters which are Servlet-level and Spring-agnostic. This is the right place for cross-cutting concerns that need to know the handler: per-endpoint auth checks, rate limiting by controller, audit logging with method-level context.
    * The controller method executes. `@RequestBody` triggers `HttpMessageConverter` (Jackson's `MappingJackson2HttpMessageConverter` by default) to deserialize the JSON body into your DTO. If `@Valid` is present, Bean Validation runs before the method body executes. On the way out, the return object is serialized back to JSON by the same converter mechanism.
    * If the controller throws an exception, `@ControllerAdvice` exception handlers catch it. These run after the controller but before the response is committed. They can transform the exception into a structured error response (like RFC 7807 Problem Details). If no handler matches, Spring returns a default error page.
    * After the controller returns (or after exception handling), `HandlerInterceptor.afterCompletion()` runs -- even if an exception was thrown. This is your cleanup hook.

    **Follow-up: A developer puts authentication logic in a HandlerInterceptor, but Spring Security uses Filters. What is the practical difference, and why does Security choose Filters?**

    The critical difference is ordering in the call chain. Filters run before DispatcherServlet, interceptors run after. If authentication is in an interceptor, the request has already been deserialized, handler mapping has been resolved, and potentially some computation has happened. With a filter, you reject unauthenticated requests before any Spring MVC processing occurs -- saving CPU cycles and preventing information leakage (a failed handler mapping lookup can reveal valid URL patterns via timing differences). Additionally, Spring Security needs to protect non-DispatcherServlet resources (actuator endpoints, static resources, other servlets in the container), which interceptors cannot do since they only apply to DispatcherServlet-routed requests.
  </Accordion>

  <Accordion title="How does Spring Security's filter chain architecture work internally? Walk me through what happens when a request with a Bearer JWT token hits a secured endpoint.">
    **Strong Answer:**

    * Spring Security registers a single Servlet Filter called `DelegatingFilterProxy` with the Servlet container. This delegates to a Spring-managed bean called `FilterChainProxy`, which is the actual brain of Spring Security. `FilterChainProxy` holds a list of `SecurityFilterChain` objects, each matching a URL pattern. For each incoming request, it finds the first matching chain and runs its filters in order.
    * A typical Security filter chain for JWT-based auth includes roughly 15 filters, but the key ones are: `CorsFilter` (handles preflight OPTIONS requests), `CsrfFilter` (which you typically disable for stateless APIs), `BearerTokenAuthenticationFilter` (extracts the JWT from the Authorization header), and `AuthorizationFilter` (checks whether the authenticated user has the required roles/authorities).
    * When the Bearer token arrives, `BearerTokenAuthenticationFilter` extracts it and creates a `BearerTokenAuthenticationToken`. It passes this to the `AuthenticationManager`, which delegates to `JwtAuthenticationProvider`. This provider uses a `JwtDecoder` (typically `NimbusJwtDecoder`) to validate the token's signature against the issuer's public key (fetched from the JWKS endpoint and cached), check expiration, and parse claims.
    * If validation succeeds, the provider creates a fully populated `Authentication` object (with authorities extracted from JWT claims like `scope` or `roles`) and stores it in the `SecurityContextHolder`. The `SecurityContextHolder` uses a `ThreadLocal` by default, which means the authentication is available anywhere in the same thread for the rest of the request.
    * The `AuthorizationFilter` then checks whether the authenticated user's authorities satisfy the endpoint's requirements (from `authorizeHttpRequests` configuration or `@PreAuthorize` annotations). If not, it throws `AccessDeniedException`, which is caught by `ExceptionTranslationFilter` further up the chain and translated into a 403 response.

    **Follow-up: In a microservices setup, should each service validate the JWT independently, or should only the gateway validate it?**

    Both, but they validate different things. The gateway should validate the token's structural integrity and expiration -- is this a valid, non-expired JWT signed by our identity provider? This rejects obviously bad requests before they consume downstream resources. Each downstream service should also validate the token for defense in depth (what if someone bypasses the gateway via direct network access in a misconfigured Kubernetes cluster), and for fine-grained authorization. The gateway does not know that `/admin/reports` on Order Service requires `ROLE_ADMIN` -- only Order Service knows its own authorization rules. In practice, share the JWKS endpoint configuration across services via Spring Cloud Config so all services validate against the same key material. The validation is just a signature check and expiration comparison -- it adds microseconds, not milliseconds.
  </Accordion>

  <Accordion title="Compare @Controller with @RestController. Then explain what @ResponseBody actually does at the HttpMessageConverter level.">
    **Strong Answer:**

    * `@Controller` marks a class as a Spring MVC controller, but its methods return view names by default. When a method returns the string `"user-profile"`, Spring's `ViewResolver` looks for a template file (Thymeleaf, JSP) with that name and renders HTML. This is the traditional server-side rendering model.
    * `@RestController` is a composed annotation: `@Controller` + `@ResponseBody`. The `@ResponseBody` annotation tells Spring to skip the ViewResolver entirely and instead write the method's return value directly to the HTTP response body using an `HttpMessageConverter`.
    * Here is what happens at the converter level: when a controller method returns an object (say, a `UserDto`), Spring iterates through its registered `HttpMessageConverter` list. For each converter, it asks "can you write this Java type for the requested media type?" The `Accept` header from the client determines the target media type. `MappingJackson2HttpMessageConverter` handles `application/json`, `MappingJackson2XmlHttpMessageConverter` handles `application/xml`, `StringHttpMessageConverter` handles `text/plain`.
    * The matching converter serializes the Java object. For Jackson, this means calling `ObjectMapper.writeValueAsBytes()`, which walks the object's getters (or fields if configured) and produces JSON. The result is written to the `HttpServletResponse` output stream with the appropriate `Content-Type` header.
    * This same mechanism works in reverse for `@RequestBody`: Spring checks the `Content-Type` header of the incoming request, finds a converter that can read that media type into the target Java type, and calls its `read()` method. This is why sending `Content-Type: text/plain` to an endpoint expecting a JSON `@RequestBody` fails with a 415 Unsupported Media Type.

    **Follow-up: How do you customize Jackson serialization globally in Spring Boot, and when would you use @JsonView instead?**

    For global customization, define a `Jackson2ObjectMapperBuilderCustomizer` bean -- this is the Spring Boot way. You can configure property naming strategy (snake\_case), date format (ISO 8601 vs. timestamps), null handling (`NON_NULL` inclusion), and module registration (Java 8 date/time module). Avoid creating your own `ObjectMapper` bean directly because it disables all of Spring Boot's auto-configuration for Jackson. `@JsonView` is a different tool: it lets you define multiple serialization "views" of the same entity. A `UserDto` might have a `Summary` view (name, avatar) and a `Detail` view (name, avatar, email, phone, address). The controller method annotated with `@JsonView(Summary.class)` only serializes fields marked with that view, avoiding the need for multiple DTO classes.
  </Accordion>

  <Accordion title="Your REST API returns 200 OK with an empty body instead of the expected JSON. No exception is logged. How do you debug this?">
    **Strong Answer:**

    * This is a classic "silent failure" pattern that usually points to one of three root causes. First, check whether the controller method returns `void` or `ResponseEntity<Void>` -- the method signature might have been changed during a refactor without updating the return statement.
    * Second, check content negotiation. If the client sends an `Accept` header that Spring cannot satisfy (e.g., `Accept: application/xml` but you only have Jackson JSON on the classpath), Spring 6+ returns 406 Not Acceptable. But older versions or misconfigured setups might return 200 with an empty body. Enable `spring.mvc.throw-exception-if-no-handler-found=true` and `spring.web.resources.add-mappings=false` to make these fail loudly.
    * Third and most subtle: check if a `HandlerInterceptor.preHandle()` is returning `false` silently. When `preHandle()` returns `false`, the request processing stops, but the response status is whatever the interceptor set (or 200 by default if it set nothing). No controller executes, no exception handler fires, and no logs appear. I have seen security interceptors do this -- they reject the request by returning `false` and writing directly to the response, but forget to set a status code.
    * To debug systematically: enable `logging.level.org.springframework.web=DEBUG`. This logs the full handler mapping resolution, which converter was selected, and what the DispatcherServlet is doing at each step. You will see entries like "Resolved handler method" or "No suitable HttpMessageConverter found" that pinpoint the exact failure point.

    **Follow-up: How would you prevent this class of bug from reaching production?**

    Write integration tests with `MockMvc` that assert both the status code and the response body structure. The pattern `mockMvc.perform(get("/users/1")).andExpect(status().isOk()).andExpect(jsonPath("$.id").exists())` catches the empty body case because `jsonPath("$.id").exists()` fails on an empty response. Also use contract testing (Spring Cloud Contract or Pact) to verify that your API's actual responses match the documented contract. The key principle: never assert only the status code in API tests. Always assert the shape of the response body.
  </Accordion>
</AccordionGroup>
