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
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. TheDispatcherServlet 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 aUser resource.
The Domain Model (DTO)
3. Exception Handling with @ControllerAdvice
Don’t let raw stack traces leak to the client. Use global exception handling.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 dependencyspring-boot-starter-validation.
Add Constraints to DTO
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 usesJackson by default to serialize/deserialize Java Objects to JSON.
- If you want XML, add
jackson-dataformat-xmldependency. - Spring will check the
Acceptheader of the request to decide whether to return JSON or XML.
6. Internal Request Lifecycle (DispatcherServlet)
Spring MVC is designed around the Front Controller pattern. TheDispatcherServlet handles all incoming requests.
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
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. CompletableFutureForkJoinPool.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:
9. Spring Security Integration
Spring Security is simply a chain of standard Servlet Filters.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
- DelegatingFilterProxy: A standard Servlet Filter (registered with Tomcat) that delegates to a Spring Bean.
- FilterChainProxy: The Spring Bean that holds all security logic. It contains a list of SecurityFilterChains.
- SecurityFilterChain: A chain of filters matching a specific URL pattern.
The Authentication Flow
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 ofAuthenticationProviders. - AuthenticationProvider: Doing the actual work (e.g.,
DaoAuthenticationProvidertalks to DB,LdapAuthenticationProvidertalks to LDAP). - UserDetailsService: Interface to load user-specific data using a username.
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
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?
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?
- The request first hits the Servlet container (Tomcat). Before it reaches any Spring code, it passes through the Servlet Filter chain. This is where
DelegatingFilterProxylives, which is how Spring Security plugs into the Servlet layer. Filters see the rawHttpServletRequestand 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 consultsHandlerMappingimplementations to find which controller method matches the URL and HTTP method.RequestMappingHandlerMappingis the one that reads@GetMapping,@PostMapping, etc. - Before the controller executes,
HandlerInterceptor.preHandle()runs. Interceptors have access to theHandlerMethodobject — 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.
@RequestBodytriggersHttpMessageConverter(Jackson’sMappingJackson2HttpMessageConverterby default) to deserialize the JSON body into your DTO. If@Validis 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,
@ControllerAdviceexception 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.
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.
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.
- Spring Security registers a single Servlet Filter called
DelegatingFilterProxywith the Servlet container. This delegates to a Spring-managed bean calledFilterChainProxy, which is the actual brain of Spring Security.FilterChainProxyholds a list ofSecurityFilterChainobjects, 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), andAuthorizationFilter(checks whether the authenticated user has the required roles/authorities). - When the Bearer token arrives,
BearerTokenAuthenticationFilterextracts it and creates aBearerTokenAuthenticationToken. It passes this to theAuthenticationManager, which delegates toJwtAuthenticationProvider. This provider uses aJwtDecoder(typicallyNimbusJwtDecoder) 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
Authenticationobject (with authorities extracted from JWT claims likescopeorroles) and stores it in theSecurityContextHolder. TheSecurityContextHolderuses aThreadLocalby default, which means the authentication is available anywhere in the same thread for the rest of the request. - The
AuthorizationFilterthen checks whether the authenticated user’s authorities satisfy the endpoint’s requirements (fromauthorizeHttpRequestsconfiguration or@PreAuthorizeannotations). If not, it throwsAccessDeniedException, which is caught byExceptionTranslationFilterfurther up the chain and translated into a 403 response.
/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.Compare @Controller with @RestController. Then explain what @ResponseBody actually does at the HttpMessageConverter level.
Compare @Controller with @RestController. Then explain what @ResponseBody actually does at the HttpMessageConverter level.
@Controllermarks a class as a Spring MVC controller, but its methods return view names by default. When a method returns the string"user-profile", Spring’sViewResolverlooks for a template file (Thymeleaf, JSP) with that name and renders HTML. This is the traditional server-side rendering model.@RestControlleris a composed annotation:@Controller+@ResponseBody. The@ResponseBodyannotation tells Spring to skip the ViewResolver entirely and instead write the method’s return value directly to the HTTP response body using anHttpMessageConverter.- Here is what happens at the converter level: when a controller method returns an object (say, a
UserDto), Spring iterates through its registeredHttpMessageConverterlist. For each converter, it asks “can you write this Java type for the requested media type?” TheAcceptheader from the client determines the target media type.MappingJackson2HttpMessageConverterhandlesapplication/json,MappingJackson2XmlHttpMessageConverterhandlesapplication/xml,StringHttpMessageConverterhandlestext/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 theHttpServletResponseoutput stream with the appropriateContent-Typeheader. - This same mechanism works in reverse for
@RequestBody: Spring checks theContent-Typeheader of the incoming request, finds a converter that can read that media type into the target Java type, and calls itsread()method. This is why sendingContent-Type: text/plainto an endpoint expecting a JSON@RequestBodyfails with a 415 Unsupported Media Type.
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.Your REST API returns 200 OK with an empty body instead of the expected JSON. No exception is logged. How do you debug this?
Your REST API returns 200 OK with an empty body instead of the expected JSON. No exception is logged. How do you debug this?
- This is a classic “silent failure” pattern that usually points to one of three root causes. First, check whether the controller method returns
voidorResponseEntity<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
Acceptheader that Spring cannot satisfy (e.g.,Accept: application/xmlbut 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. Enablespring.mvc.throw-exception-if-no-handler-found=trueandspring.web.resources.add-mappings=falseto make these fail loudly. - Third and most subtle: check if a
HandlerInterceptor.preHandle()is returningfalsesilently. WhenpreHandle()returnsfalse, 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 returningfalseand 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.
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.