Spring MVC Interview Questions – Architecture, DispatcherServlet and Request Handling

  • Last Updated: April 30, 2026
  • By: javahandson
  • Series
img

Spring MVC Interview Questions – Architecture, DispatcherServlet and Request Handling

Spring MVC interview questions are among the most common topics tested for Java backend roles — and for good reason. Spring MVC is the web layer of the Spring Framework, and senior engineers are expected to go well beyond annotations. Whether you are interviewing for a backend engineer role, a full-stack Java position, or a Spring Boot architect seat, the interviewer will always ask how Spring MVC handles HTTP requests, what the DispatcherServlet does internally, and how the various components wire together. Top product companies expect you to explain the full request lifecycle, contrast HandlerMapping strategies, explain ViewResolver chains, and describe how CORS, multipart uploads, and content negotiation work under the hood.

This article covers 15 carefully crafted Spring MVC interview questions — ranging from beginner to advanced — focusing on the MVC design pattern, DispatcherServlet internals, annotation-based controllers, parameter binding, view resolution, content negotiation, file uploads, CORS, and the Model layer. Every answer includes the technical depth and the Interview Insight notes that distinguish strong candidates in product company interviews.

Q1. What is the MVC design pattern — Model, View, Controller responsibilities?

Answer – MVC stands for Model-View-Controller, an architectural pattern that separates an application into three distinct layers. Each layer has a single, well-defined responsibility, and the separation ensures that changes in one layer do not ripple into the others. This makes the codebase easier to test, maintain, and evolve independently.

The Model represents the application’s data and business logic. It is the layer that knows how to retrieve, store, and process data — typically by interacting with databases, external services, or in-memory stores. The Model has no knowledge of how the data is displayed or what HTTP requests triggered the operation. In a Spring application, the Model is usually your service classes backed by repository classes.

The View is the presentation layer. Its sole job is to render data from the Model into a format that the client can consume — historically an HTML page, but in modern REST APIs it is typically JSON or XML serialized by Jackson or JAXB. The View does not contain business logic. It receives a model object (a map of key-value pairs) and renders it. In Spring MVC, view technologies include Thymeleaf, FreeMarker, JSP, and for REST APIs, the message converters that produce JSON/XML output.

The Controller is the traffic cop between the View and the Model. It receives incoming HTTP requests, delegates to the appropriate service or repository, assembles the Model with the results, and selects the View to render. In Spring MVC, controllers are annotated with @Controller or @RestController, and handler methods are mapped to URLs via @RequestMapping.

LayerResponsibility in Spring MVC
ModelBusiness logic, data retrieval, domain objects — @Service, @Repository
ViewRendering output — Thymeleaf, JSP, FreeMarker, or JSON via MessageConverter
ControllerHandles HTTP request, delegates to Model, selects View — @Controller / @RestController

The key benefit of MVC is loose coupling. A designer can change HTML templates without touching Java code. A developer can add a new REST endpoint without touching the rendering layer. Unit tests can target the service layer in isolation without spinning up a web server.

🎯 Interview Insight:  Interviewers often follow up with: ‘Is Spring MVC a strict MVC framework?’ The answer is nuanced. Spring MVC is a front-controller MVC framework — the DispatcherServlet acts as the single front controller that dispatches all requests. Traditional MVC frameworks create one controller per form or page. Spring MVC uses a central dispatcher with multiple handler methods, which is more flexible and scalable for large applications.

Q2. What is DispatcherServlet? Explain the full request lifecycle step by step.

Answer – DispatcherServlet is the heart of Spring MVC. It is a standard Java Servlet that acts as the front controller for the entire Spring web application. Every HTTP request that enters the application first hits the DispatcherServlet, which orchestrates the full lifecycle of the request — from handler lookup through to response rendering.

The full request lifecycle inside DispatcherServlet proceeds in the following sequence:

HTTP Request arrives at DispatcherServlet

Step 1 — LocaleResolver and ThemeResolver
  DispatcherServlet calls LocaleResolver.resolveLocale() to determine the locale
  for i18n. ThemeResolver resolves the UI theme if in use.

Step 2 — HandlerMapping lookup
  DispatcherServlet iterates HandlerMapping implementations (RequestMappingHandlerMapping,
  BeanNameUrlHandlerMapping, etc.) and asks each: 'Do you handle this URL?'
  The first matching HandlerMapping returns a HandlerExecutionChain,
  which wraps the Handler (controller method) + a list of HandlerInterceptors.

Step 3 — HandlerInterceptor.preHandle()
  Each interceptor in the chain runs preHandle(). If any returns false,
  processing stops and the response is written directly.

Step 4 — HandlerAdapter selection
  DispatcherServlet finds a HandlerAdapter that supports the handler type.
  For @RequestMapping methods this is RequestMappingHandlerAdapter.

Step 5 — Handler (controller method) execution
  HandlerAdapter.handle() invokes the actual controller method via reflection.
  Argument resolvers bind request parameters, path variables, request body, etc.
  The return value is processed by HandlerMethodReturnValueHandler.

Step 6 — HandlerInterceptor.postHandle()
  Runs after the handler but before the view is rendered.
  Can add model attributes or modify the ModelAndView.

Step 7 — Exception handling
  If any exception was thrown, DispatcherServlet tries HandlerExceptionResolver
  implementations (ExceptionHandlerExceptionResolver for @ExceptionHandler,
  ResponseStatusExceptionResolver, DefaultHandlerExceptionResolver).

Step 8 — View resolution and rendering
  If a view name was returned, DispatcherServlet asks ViewResolver implementations
  to resolve the name to a View object. The View renders the model to the response.
  For @RestController / @ResponseBody, this step is skipped — the message
  converter writes directly to the response in Step 5.

Step 9 — HandlerInterceptor.afterCompletion()
  Always runs (even on exception). Used for resource cleanup, logging, etc.

The key insight is that DispatcherServlet does not perform this work itself — it delegates entirely to pluggable strategy objects (HandlerMapping, HandlerAdapter, ViewResolver, HandlerExceptionResolver). This is the Strategy pattern applied at the framework level, which is what makes Spring MVC extensible.

🎯 Interview Insight:  A common interview question: ‘What happens if no HandlerMapping matches the incoming request?’ DispatcherServlet throws a NoHandlerFoundException. By default, this is mapped to a 404 response by DefaultHandlerExceptionResolver. If spring.mvc.throw-exception-if-no-handler-found=true is set, the exception propagates to your @ExceptionHandler. Without this flag, Spring silently returns 404 via the default error page mechanism.

Q3. How is DispatcherServlet initialized internally — what beans does it look for?

Answer – DispatcherServlet extends FrameworkServlet, which extends HttpServletBean, which extends the standard Java HttpServlet. During Servlet container startup, the container calls HttpServlet.init(). This triggers FrameworkServlet.initServletBean(), which creates or refreshes the WebApplicationContext associated with the servlet.

In a Spring Boot application, the DispatcherServlet is auto-configured by DispatcherServletAutoConfiguration. It registers the servlet with the embedded Tomcat/Jetty/Undertow container via a DispatcherServletRegistrationBean. The associated WebApplicationContext is the same root ApplicationContext created by SpringApplication.run().

Once the WebApplicationContext is ready, DispatcherServlet.onRefresh() calls initStrategies(). This method initializes nine strategy areas by looking for specific beans in the WebApplicationContext. If none are found, it falls back to DispatcherServlet.properties for defaults:

StrategyDefault Bean / Fallback
MultipartResolverNo default — must declare CommonsMultipartResolver or StandardServletMultipartResolver
LocaleResolverAcceptHeaderLocaleResolver — reads Accept-Language header
ThemeResolverFixedThemeResolver
HandlerMappingRequestMappingHandlerMapping + BeanNameUrlHandlerMapping
HandlerAdapterRequestMappingHandlerAdapter + SimpleControllerHandlerAdapter
HandlerExceptionResolverExceptionHandlerExceptionResolver + ResponseStatusExceptionResolver + DefaultHandlerExceptionResolver
RequestToViewNameTranslatorDefaultRequestToViewNameTranslator
ViewResolverInternalResourceViewResolver
FlashMapManagerSessionFlashMapManager

Spring Boot auto-configuration registers most of these beans via WebMvcAutoConfiguration and its inner classes. This is why Spring Boot MVC works out of the box without XML configuration. The @EnableWebMvc annotation on a @Configuration class imports DelegatingWebMvcConfiguration, which assembles all of these strategy beans from @Bean methods across all WebMvcConfigurer implementations.

🎯 Interview Insight:  A subtle but important detail: DispatcherServlet uses a child WebApplicationContext in traditional Spring MVC (not Spring Boot). The root context holds service and repository beans; the child context holds web-tier beans (controllers, view resolvers, handler mappings). In Spring Boot, there is typically a single ApplicationContext — no root/child split — unless you explicitly configure servlet context hierarchy.

Q4. What is HandlerMapping — what types exist and how does Spring choose one?

Answer – HandlerMapping is the Spring MVC strategy responsible for determining which handler (controller method or bean) should process a given HTTP request. The DispatcherServlet iterates all registered HandlerMapping instances in order of priority and asks each one to map the incoming request. The first HandlerMapping that returns a non-null HandlerExecutionChain wins.

Spring MVC ships with several HandlerMapping implementations, each suited to a different handler registration style:

HandlerMappingPurpose
RequestMappingHandlerMappingMaps requests to @RequestMapping-annotated controller methods. This is the primary handler mapping in annotation-driven Spring MVC and has the highest default priority.
BeanNameUrlHandlerMappingMaps a URL directly to a bean name. A bean named ‘/home’ handles requests to /home. Used with older XML-configured controllers.
RouterFunctionMappingMaps requests to RouterFunction definitions used in Spring WebFlux-style functional endpoints in Spring MVC.
SimpleUrlHandlerMappingMaps URLs to handler beans via an explicitly configured map. Used for static resource serving (ResourceHttpRequestHandler).
WelcomePageHandlerMappingSpring Boot specific — maps ‘/’ to the static index.html welcome page if present.

HandlerMapping selection is priority-based. Each HandlerMapping bean can implement Ordered or be annotated with @Order. RequestMappingHandlerMapping has order 0 by default, making it the first candidate. If it matches, the others are never consulted. If it returns null (no matching @RequestMapping method), the next HandlerMapping in order is tried.

When RequestMappingHandlerMapping finds a match, it returns a HandlerExecutionChain. This chain contains the HandlerMethod (the exact controller method to invoke) plus the ordered list of HandlerInterceptors that apply to this request based on URL pattern matching.

🎯 Interview Insight:  Interviewers sometimes ask: ‘How does Spring resolve ambiguous @RequestMapping mappings — e.g., two methods that both match /orders/{id}?’ Spring throws AmbiguousRequestMappingException at startup. It does not silently pick one. The mapping registry builds a lookup structure at startup time, and any ambiguity is caught immediately. This is one reason why Spring Boot applications fail fast on startup rather than serving incorrect responses at runtime.

Q5. What is HandlerAdapter, and why does Spring need it?

Answer – HandlerAdapter is the bridge between DispatcherServlet and the actual handler object. The reason Spring needs it is that Spring MVC supports different types of handlers — annotated controller methods, plain Controller interface implementations, HttpRequestHandler implementations, and more. Each handler type has a different invocation signature. DispatcherServlet cannot know at compile time how to invoke all these handler types directly. Instead, it delegates to a HandlerAdapter that knows exactly how to call a specific handler type.

The contract is simple: HandlerAdapter.supports(handler) returns true if the adapter knows how to invoke that handler. HandlerAdapter.handle(request, response, handler) performs the actual invocation and returns a ModelAndView (or null for @ResponseBody responses).

HandlerAdapterHandles
RequestMappingHandlerAdapterHandles HandlerMethod objects (methods annotated with @RequestMapping). Resolves arguments via HandlerMethodArgumentResolver, processes return values via HandlerMethodReturnValueHandler. This is the adapter used for all annotation-driven controllers.
SimpleControllerHandlerAdapterHandles beans implementing the Controller interface. Calls the legacy ModelAndView handleRequest(request, response) method.
HttpRequestHandlerAdapterHandles beans implementing HttpRequestHandler. Used internally for serving static resources via ResourceHttpRequestHandler.
HandlerFunctionAdapterHandles functional endpoints defined via RouterFunction / HandlerFunction.

The most important adapter in modern Spring MVC is RequestMappingHandlerAdapter. It is responsible for the entire parameter-binding and return-value-processing pipeline. It has a list of HandlerMethodArgumentResolvers — for @RequestParam, @PathVariable, @RequestBody, @RequestHeader, HttpServletRequest, @ModelAttribute, and many more — and a list of HandlerMethodReturnValueHandlers for @ResponseBody, ModelAndView, String view name, ResponseEntity, and others. This composable resolver/handler chain is what makes Spring MVC so extensible.

🎯 Interview Insight:  A common follow-up: ‘Can you add a custom argument resolver?’ Yes. Implement HandlerMethodArgumentResolver, override supportsParameter() and resolveArgument(), then register it via WebMvcConfigurer.addArgumentResolvers(). This is how libraries like Spring Data Web add @PageableDefault and Pageable argument injection without modifying the framework core.

Q6. What is @Controller vs @RestController — what does @ResponseBody actually do?

Answer – @Controller is a stereotype annotation that marks a class as a Spring MVC controller. Handler methods in a @Controller class return a view name (a String) or a ModelAndView object by default. The DispatcherServlet passes this view name to the ViewResolver, which resolves it to a template (Thymeleaf, JSP, FreeMarker), renders the model into that template, and writes HTML to the response.

@RestController is a composed annotation — it is @Controller + @ResponseBody applied at the class level. When @ResponseBody is present on a method (or inherited from a class-level annotation), the return value of that method is NOT passed to the ViewResolver. Instead, RequestMappingHandlerAdapter uses an HttpMessageConverter to serialize the return value directly into the HTTP response body. For objects, this typically means Jackson’s MappingJackson2HttpMessageConverter, which writes JSON.

// @Controller — returns a view name
@Controller
public class HomeController {
    @GetMapping("/home")
    public String home(Model model) {
        model.addAttribute("user", userService.currentUser());
        return "home"; // ViewResolver resolves to /templates/home.html
    }
}

// @RestController — returns JSON via HttpMessageConverter
@RestController
@RequestMapping("/api/orders")
public class OrderController {
    @GetMapping("/{id}")
    public ResponseEntity<OrderDto> getOrder(@PathVariable Long id) {
        // return value is serialised to JSON by Jackson — no ViewResolver involved
        return ResponseEntity.ok(orderService.findById(id));
    }
}

// Equivalent: @Controller + @ResponseBody per method
@Controller
public class MixedController {
    @GetMapping("/page")
    public String renderPage() { return "page"; } // renders a template

    @GetMapping("/api/data")
    @ResponseBody
    public List<Item> getJson() { return itemService.findAll(); } // writes JSON
}

The distinction matters in practice. A @Controller with @ResponseBody on specific methods is useful when you need both HTML endpoints and API endpoints in the same controller. For pure REST APIs, @RestController eliminates the need to annotate each method with @ResponseBody and makes the class’s intent explicit.

🎯 Interview Insight:  @ResponseBody triggers content negotiation. When a @ResponseBody method returns an object, Spring iterates its registered HttpMessageConverters and picks the one that matches both the return type and the Accept header sent by the client. If the client sends Accept: application/json and Jackson is on the classpath, MappingJackson2HttpMessageConverter handles it. If Accept: application/xml is sent and Jackson XML is present, it produces XML. This is the same content negotiation mechanism used by ResponseEntity.

Q7. What is @RequestMapping and its shortcut variants?

Answer – @RequestMapping is the foundational annotation for mapping HTTP requests to handler methods in Spring MVC. It can be applied at both the class and method levels. At the class level, it defines a base URL path that is prepended to all method-level mappings within that controller. At the method level, it narrows the mapping to specific HTTP methods, paths, headers, parameters, or media types.

@Controller
@RequestMapping("/api/products") // base path applied to all methods
public class ProductController {

    // Explicit @RequestMapping with method
    @RequestMapping(method = RequestMethod.GET)
    public List<Product> getAll() { ... }

    // With path variable and media type constraint
    @RequestMapping(value = "/{id}", method = RequestMethod.GET,
                    produces = "application/json")
    public Product getById(@PathVariable Long id) { ... }

    // Headers and params constraints (less common in practice)
    @RequestMapping(value = "/search", method = RequestMethod.GET,
                    params = "q", headers = "X-API-Version=2")
    public List<Product> searchV2(@RequestParam String q) { ... }
}

Because specifying method = RequestMethod.GET (or POST, PUT, etc.) on every @RequestMapping annotation is verbose; Spring 4.3 introduced composed mapping annotations as shortcuts. These are meta-annotations built on top of @RequestMapping with a fixed HTTP method:

Shortcut AnnotationEquivalent @RequestMapping
@GetMapping(“/path”)@RequestMapping(value = “/path”, method = RequestMethod.GET)
@PostMapping(“/path”)@RequestMapping(value = “/path”, method = RequestMethod.POST)
@PutMapping(“/path”)@RequestMapping(value = “/path”, method = RequestMethod.PUT)
@DeleteMapping(“/path”)@RequestMapping(value = “/path”, method = RequestMethod.DELETE)
@PatchMapping(“/path”)@RequestMapping(value = “/path”, method = RequestMethod.PATCH)

@RequestMapping also supports Ant-style path patterns (/api/**), URI template variables (/{id}), and regular expressions in path variables (/{id:\\d+}). In Spring 5.3+, the default path-matching strategy was changed from AntPathMatcher to PathPatternParser to improve performance, especially with many registered routes.

🎯 Interview Insight:  A subtle detail about @RequestMapping at the class level: if you omit it entirely, all method-level paths are absolute. If you add an empty @RequestMapping at the class level (with no value), it still maps to the root path ‘/’. More importantly, @RequestMapping at the class level is not inherited by subclasses in the same way as regular Java method inheritance — each controller class is registered independently in the HandlerMapping registry.

Q8. @RequestParam vs @PathVariable vs @RequestBody vs @RequestHeader — differences?

Answer – These four annotations are the primary ways to extract data from different parts of an HTTP request in Spring MVC. Each targets a distinct part of the request structure, and choosing the correct one depends on where the data lives in the request.

// @PathVariable — extracts from URI template segment
// URL: GET /orders/42
@GetMapping("/orders/{id}")
public Order getOrder(@PathVariable Long id) { ... }

// Multiple path variables
@GetMapping("/users/{userId}/orders/{orderId}")
public Order getUserOrder(@PathVariable Long userId,
                          @PathVariable Long orderId) { ... }

// @RequestParam — extracts from query string or form data
// URL: GET /orders?status=PENDING&page=0&size=20
@GetMapping("/orders")
public Page<Order> listOrders(
    @RequestParam(defaultValue = "PENDING") String status,
    @RequestParam(defaultValue = "0")       int page,
    @RequestParam(defaultValue = "20")      int size) { ... }

// @RequestParam with required=false — optional parameter
@GetMapping("/search")
public List<Product> search(@RequestParam(required = false) String category) { ... }

// @RequestBody — deserialises the HTTP request body (e.g. JSON → Java object)
// POST /orders with body: {"productId": 5, "quantity": 2}
@PostMapping("/orders")
public ResponseEntity<Order> createOrder(@RequestBody @Valid CreateOrderRequest req) {
    Order order = orderService.create(req);
    return ResponseEntity.created(URI.create("/orders/" + order.getId())).body(order);
}

// @RequestHeader — extracts a specific HTTP request header
@GetMapping("/profile")
public UserProfile getProfile(
    @RequestHeader("Authorization") String authToken,
    @RequestHeader(value = "X-Tenant-Id", required = false) String tenantId) { ... }
AnnotationSource in HTTP Request
@PathVariableURI template — the {variable} segment of the URL path. Typically used to identify a resource by ID.
@RequestParamQuery string (?key=value) or form-encoded body. For GET requests with filter/pagination params, and for HTML form submissions.
@RequestBodyThe raw HTTP request body. Deserialised by an HttpMessageConverter. Used for JSON/XML payloads in POST/PUT/PATCH requests. Only one @RequestBody is allowed per method.
@RequestHeaderHTTP request headers. Used for Authorization tokens, custom correlation IDs, tenant identifiers, or any header-transported metadata.
🎯 Interview Insight:  A common interview trap: ‘Can you use @RequestBody with a GET request?’ Technically, yes — nothing in the HTTP spec prohibits a body on GET. Spring will accept it. However, it violates REST semantics, and many proxies, caches, and load balancers strip GET request bodies. In practice, @RequestBody is only used with POST, PUT, and PATCH. Also: @RequestParam can bind directly to a POJO if the parameter names match the object’s field names, which is useful for form submissions without the overhead of @RequestBody JSON parsing.

Q9. What is ViewResolver — what implementations exist?

Answer – ViewResolver is a Spring MVC strategy that maps a logical view name (a String returned by a controller method) to a concrete View object that renders the model to the HTTP response. When a controller method returns a String like ‘orderDetails’, DispatcherServlet passes that name to its list of registered ViewResolvers. Each resolver is called in order of priority until one returns a non-null View.

ViewResolverDescription
InternalResourceViewResolverDefault fallback. Maps a view name to a JSP file using configurable prefix and suffix. Example: prefix=’/WEB-INF/views/’, suffix=’.jsp’ maps ‘home’ to /WEB-INF/views/home.jsp. Uses InternalResourceView or JstlView.
ThymeleafViewResolverResolves view names to Thymeleaf HTML templates. Auto-configured by Spring Boot Thymeleaf starter. Maps ‘home’ to classpath:templates/home.html.
FreeMarkerViewResolverResolves view names to FreeMarker .ftl templates.
BeanNameViewResolverLooks up a bean in the ApplicationContext whose name matches the view name. Useful for programmatically constructed views.
ContentNegotiatingViewResolverDelegates to other ViewResolvers but selects the best match based on the request’s Accept header or URL extension. Enables a single handler method to return HTML or JSON based on the client’s preference.
XmlViewResolver / ResourceBundleViewResolverXML-file-based and properties-file-based view mappings. Rarely used in modern Spring Boot applications.
// Custom ViewResolver configuration (without Spring Boot auto-config)
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Bean
    public InternalResourceViewResolver jspViewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".jsp");
        resolver.setOrder(2); // lower priority than Thymeleaf
        return resolver;
    }

    // Thymeleaf auto-configured by Spring Boot, order=1 by default
}

When multiple ViewResolvers are registered, DispatcherServlet iterates them in order. The first one that returns a non-null View object is used. For ViewResolvers that may throw an exception if the view file does not exist (such as InternalResourceViewResolver), Spring handles this gracefully and proceeds to the next resolver. However, InternalResourceViewResolver should always be registered last because it returns a non-null view for any name without actually checking whether the file exists.

🎯 Interview Insight:  For @RestController methods (or methods annotated with @ResponseBody), the ViewResolver chain is completely bypassed. The DispatcherServlet detects that the response has already been committed to the output stream by the HttpMessageConverter, sets the request attribute indicating the view has been rendered, and skips the view resolution phase entirely. This is an important detail: ViewResolver is only relevant for server-side template rendering.

Q10. How does content negotiation work in Spring MVC?

Answer – Content negotiation is the mechanism by which Spring MVC determines the response media type when a controller method can produce multiple formats. The client expresses its preference using the Accept request header (e.g., Accept: application/json, text/html;q=0.9). Spring MVC selects the best-matching HttpMessageConverter or ViewResolver based on this preference.

Spring’s ContentNegotiationManager coordinates the process. It supports three strategies for determining the requested media type, evaluated in order:

The first strategy is the Accept header. This is the standard HTTP mechanism. When a REST client sends an Accept header of application/json, Spring selects a JSON message converter. This strategy is always active and is the primary mechanism for REST APIs.

The second strategy is URL path extension (deprecated since Spring 5.3). A request to /orders/42.json would be treated as a JSON request. This was popular with browser-based clients that could not set Accept headers. It is disabled by default in modern Spring Boot because it creates ambiguity with actual file extensions in paths.

The third strategy is a request parameter. By configuring ContentNegotiationManager with a parameter strategy, /orders/42?format=json can be used to specify JSON. This is useful for APIs consumed by simple HTTP clients that cannot set headers.

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        configurer
            .favorParameter(true)           // enable ?format=json strategy
            .parameterName("format")         // use ?format= as the parameter name
            .ignoreAcceptHeader(false)       // still honour Accept header
            .defaultContentType(MediaType.APPLICATION_JSON)
            .mediaType("json", MediaType.APPLICATION_JSON)
            .mediaType("xml",  MediaType.APPLICATION_XML);
    }
}

// Single controller method serving JSON or XML based on Accept header
@RestController
@RequestMapping("/api/orders")
public class OrderController {
    @GetMapping(value = "/{id}",
                produces = {MediaType.APPLICATION_JSON_VALUE,
                            MediaType.APPLICATION_XML_VALUE})
    public OrderDto getOrder(@PathVariable Long id) {
        return orderService.findById(id);
        // Jackson produces JSON if Accept: application/json
        // Jackson XML produces XML if Accept: application/xml
    }
}

The produces attribute on @RequestMapping restricts which media types a method can produce. If a client requests a media type not listed in the produces attribute, Spring returns a 406 Not Acceptable before the method is even called. This serves as both a declaration of capability and a filter.

🎯 Interview Insight:  An advanced point about content negotiation and the order of HttpMessageConverters: Spring iterates converters in registration order. ByteArrayHttpMessageConverter is first (handles byte[]). StringHttpMessageConverter is second. ResourceHttpMessageConverter is third. MappingJackson2HttpMessageConverter is further down the list. If two converters both claim to support the requested media type, the first one wins. This matters when you have both Jackson and JAXB (for XML) on the classpath — registration order determines which one handles ambiguous cases.

Q11. What is @ResponseStatus and when do you use it?

Answer – @ResponseStatus is an annotation that sets the HTTP response status code (and optionally a reason phrase) for a handler method or an exception class. It is a clean, declarative way to control the HTTP status code without manually creating a ResponseEntity or calling HttpServletResponse.setStatus().

On a controller method, @ResponseStatus sets the response code for successful invocations. The most common use is marking creation endpoints with 201 Created:

// On a handler method — sets 201 Created for all successful calls
@PostMapping("/orders")
@ResponseStatus(HttpStatus.CREATED)
public OrderDto createOrder(@RequestBody @Valid CreateOrderRequest req) {
    return orderService.create(req);
    // No need to return ResponseEntity.created(...) — status is set declaratively
}

// On an exception class — maps the exception to a specific HTTP status
@ResponseStatus(value = HttpStatus.NOT_FOUND,
                reason = "Order not found")
public class OrderNotFoundException extends RuntimeException {
    public OrderNotFoundException(Long id) {
        super("Order " + id + " not found");
    }
}

// When this exception is thrown anywhere, Spring returns:
// HTTP 404 Not Found
// Body: {"status": 404, "error": "Not Found", "message": "Order not found"}

When @ResponseStatus is placed on an exception class, Spring’s ResponseStatusExceptionResolver detects it. When that exception is thrown by a handler method and is not caught by a more specific @ExceptionHandler, ResponseStatusExceptionResolver reads the annotation and sets the response status accordingly.

UsageBehaviour
@ResponseStatus on methodSets response code for all successful invocations of that method. Does not affect exception paths.
@ResponseStatus on exception classResponseStatusExceptionResolver maps that exception to the specified HTTP status whenever it is thrown from a handler.
ResponseStatusException (runtime)Programmatic alternative — throw new ResponseStatusException(HttpStatus.NOT_FOUND, “reason”). No annotation needed. Preferred in Spring 5+.
🎯 Interview Insight:  A common interview question: ‘What is the difference between @ResponseStatus on a method and returning ResponseEntity?’ @ResponseStatus is a compile-time declaration — it always sets the same status code. ResponseEntity is a runtime decision — you can conditionally return 200, 201, or 204 from the same method depending on logic. For conditional status codes (e.g., 201 if created, 200 if updated), always use ResponseEntity. @ResponseStatus is best for endpoints where the status code is always the same.

Q12. How does multipart file upload work — MultipartFile, config, size limits?

Answer – File upload in Spring MVC is handled by the multipart/form-data content type. When a client submits a multipart form or sends a file via HTTP POST with Content-Type: multipart/form-data, Spring routes the request through a MultipartResolver before it reaches the controller. The MultipartResolver parses the raw multipart request body and wraps it in a MultipartHttpServletRequest, which makes individual parts accessible by name.

Spring Boot auto-configures StandardServletMultipartResolver via MultipartAutoConfiguration. This resolver delegates to the Servlet 3.0+ container’s native multipart parsing (Tomcat, Jetty, Undertow). The older CommonsMultipartResolver (backed by Apache Commons FileUpload) is still available but is not the default in Spring Boot.

// Simple single-file upload
@RestController
@RequestMapping("/api/files")
public class FileUploadController {

    @PostMapping("/upload")
    public ResponseEntity<String> uploadFile(
            @RequestParam("file") MultipartFile file) throws IOException {

        if (file.isEmpty()) {
            return ResponseEntity.badRequest().body("No file selected");
        }

        String originalName = file.getOriginalFilename();
        String contentType  = file.getContentType();   // e.g. "image/png"
        long   size         = file.getSize();          // bytes
        byte[] bytes        = file.getBytes();         // full content in memory

        // Save to disk using transferTo
        Path target = Paths.get("/uploads/" + originalName);
        file.transferTo(target);

        return ResponseEntity.ok("Uploaded: " + originalName + " (" + size + " bytes)");
    }

    // Multiple files
    @PostMapping("/upload-multiple")
    public ResponseEntity<List<String>> uploadMultiple(
            @RequestParam("files") List<MultipartFile> files) {
        List<String> names = files.stream()
            .map(MultipartFile::getOriginalFilename)
            .collect(Collectors.toList());
        return ResponseEntity.ok(names);
    }

    // Mixed: file + form fields
    @PostMapping("/upload-with-metadata")
    public ResponseEntity<String> uploadWithMeta(
            @RequestParam("file")        MultipartFile file,
            @RequestParam("description") String description,
            @RequestParam("category")    String category) {
        // both file and form fields bound in one method
        return ResponseEntity.ok("OK");
    }
}

File upload size limits are controlled by Spring Boot application properties:

# application.properties
spring.servlet.multipart.enabled=true
spring.servlet.multipart.max-file-size=10MB      # per-file limit
spring.servlet.multipart.max-request-size=50MB   # total request body limit
spring.servlet.multipart.file-size-threshold=2KB # threshold before writing to disk
spring.servlet.multipart.location=/tmp/uploads   # temp directory for disk spill

When a file exceeds max-file-size, Spring throws MaxUploadSizeExceededException (which wraps a MultipartException). This should be caught in a @ControllerAdvice @ExceptionHandler to return a clean 413 Payload Too Large response rather than letting the container’s default error page fire.

🎯 Interview Insight:  A production-critical detail: MultipartFile.getBytes() loads the entire file into JVM heap memory. For large files (video, high-resolution images), this can cause OutOfMemoryErrors. The correct approach for large files is to stream directly to disk using MultipartFile.transferTo(Path) or to stream to a cloud storage service (S3, GCS) using MultipartFile.getInputStream() without ever materialising the bytes in memory. Set file-size-threshold appropriately so that files above the threshold are spilled to disk during multipart parsing rather than accumulating in memory.

Q13. What is the difference between @RequestMapping at the class level vs the method level?

Answer – @RequestMapping at the class level and at the method level serve complementary but distinct roles. Understanding how they compose together is important for structuring clean, maintainable Spring MVC controllers.

A class-level @RequestMapping defines a base path prefix that applies to all handler methods in the controller. It also narrows the entire controller by other attributes like headers, params, or consumes/produces. A method-level @RequestMapping (or its shortcut variants) further narrows the mapping by appending to the class-level path and specifying the HTTP method, additional path segments, or media type constraints.

@RestController
@RequestMapping("/api/v2/orders") // base path for all methods
public class OrderController {

    // Final URL: GET /api/v2/orders
    @GetMapping
    public List<OrderDto> listOrders() { ... }

    // Final URL: GET /api/v2/orders/{id}
    @GetMapping("/{id}")
    public OrderDto getById(@PathVariable Long id) { ... }

    // Final URL: POST /api/v2/orders
    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public OrderDto create(@RequestBody CreateOrderRequest req) { ... }

    // Final URL: PUT /api/v2/orders/{id}
    @PutMapping("/{id}")
    public OrderDto update(@PathVariable Long id,
                           @RequestBody UpdateOrderRequest req) { ... }

    // Final URL: DELETE /api/v2/orders/{id}
    @DeleteMapping("/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void delete(@PathVariable Long id) { ... }
}

// Class-level @RequestMapping can also restrict by other attributes
@RestController
@RequestMapping(value = "/api/admin",
                headers = "X-Admin-Token") // ALL methods require this header
public class AdminController { ... }
AttributeClass-Level Behaviour
Restricts all methods to specific HTTP methods. Can be narrowed further at the method level.Defines a base path prefix. Method paths are appended to it.
methodRestricts all methods to specific HTTP methods. Can be narrowed further at method level.
consumesRestricts all handler methods to accept only the specified Content-Type. Useful for controllers that only handle JSON.
producesRestricts all handler methods to produce only the specified media types.
headersAll handler methods require the specified request headers to be present.
paramsAll handler methods require the specified request parameters.
🎯 Interview Insight:  A practical design tip that interviewers appreciate: class-level @RequestMapping with a versioned path (/api/v1/orders, /api/v2/orders) is the cleanest way to manage API versioning in Spring MVC. You keep v1 and v2 controllers as separate classes, each with its own class-level path. This avoids conditional logic within methods and keeps the code for each API version in one place, making deprecating older versions straightforward — you simply delete the v1 controller class.

Q14. What are the differences between Model, ModelMap, and ModelAndView?

Answer – Model, ModelMap, and ModelAndView are three mechanisms for passing data from a controller method to a view template in Spring MVC. They are primarily relevant for server-side rendering scenarios using Thymeleaf, JSP, or FreeMarker. For REST APIs using @ResponseBody or @RestController, the return value is serialized directly to JSON/XML.

A model is the simplest abstraction. It is an interface in org.springframework.ui. A controller method declares Model as a parameter, and Spring injects a pre-instantiated implementation (an ExtendedModelMap). The method adds attributes via model.addAttribute(key, value), and the view accesses them by key. The view name is returned as a String from the method.

ModelMap extends LinkedHashMap<String, Object> and implements Model. It offers the same addAttribute API but also allows map-style access via .get(key). It is a concrete class rather than an interface, which is occasionally useful when you need to inspect its contents programmatically (e.g., in tests). In practice, Model and ModelMap are interchangeable in method signatures.

ModelAndView is a container that holds both the Model data and the view name (or View object) in one object. It is returned directly from the controller method instead of a String. This was the primary pattern in pre-Spring 3.x controllers. It is still valid and occasionally useful when the view selection is conditional — you can populate the ModelAndView differently before returning based on business logic.

// Model — inject and return view name
@Controller
public class OrderViewController {

    @GetMapping("/orders/{id}")
    public String viewOrder(@PathVariable Long id, Model model) {
        model.addAttribute("order",    orderService.findById(id));
        model.addAttribute("statuses", OrderStatus.values());
        return "orderDetail"; // ViewResolver resolves this
    }
}

// ModelMap — same result, concrete class
@Controller
public class ProductViewController {

    @GetMapping("/products")
    public String listProducts(ModelMap modelMap) {
        modelMap.addAttribute("products", productService.findAll());
        return "productList";
    }
}

// ModelAndView — holds model + view together
@Controller
public class ReportController {

    @GetMapping("/report")
    public ModelAndView report(@RequestParam String type) {
        ModelAndView mav = new ModelAndView();
        if ("summary".equals(type)) {
            mav.setViewName("summaryReport");
            mav.addObject("data", reportService.summary());
        } else {
            mav.setViewName("detailReport");
            mav.addObject("data", reportService.detail());
        }
        return mav;
    }
}
TypeKey Characteristics
ModelInterface injected as method parameter. Clean API — addAttribute(key, value). View name returned separately as String. Most common in modern Spring MVC.
ModelMapConcrete class (extends LinkedHashMap), implements Model. Same capabilities as Model. Allows map-style read access. Slightly more verbose injection signature.
ModelAndViewBundles model attributes and view name/object into a single return value. Useful for conditional view selection in a single method. Older pattern — still valid.
🎯 Interview Insight:  @ModelAttribute is the bridge between the form and the Model. When used as a method parameter (@ModelAttribute User user), Spring binds form fields or request parameters to the object fields by name. When used on a method in a controller, @ModelAttribute annotated methods run before every handler method in that controller and pre-populate the model. This is how shared model data (e.g., the logged-in user, navigation categories) is made available to all views in a controller without repeating code in every handler method.

Q15. What is @CrossOrigin, and how does CORS work in Spring MVC?

Answer – CORS — Cross-Origin Resource Sharing — is a browser security mechanism that blocks JavaScript running on one origin (protocol + domain + port) from making HTTP requests to a different origin. When a React application at http://localhost:3000 calls a Spring Boot API at http://localhost:8080, the browser first sends a preflight OPTIONS request to check whether the server allows cross-origin requests. If the server does not respond with the correct Access-Control-Allow-* headers, the browser blocks the actual request.

Spring MVC handles CORS through CorsProcessor and the configured CorsConfiguration. The simplest way to enable CORS in Spring MVC is @CrossOrigin, which can be placed on a controller class or a handler method:

// @CrossOrigin on a single method
@RestController
@RequestMapping("/api/orders")
public class OrderController {

    @CrossOrigin(origins = "http://localhost:3000")
    @GetMapping("/{id}")
    public OrderDto getOrder(@PathVariable Long id) { ... }
}

// @CrossOrigin on the entire controller
@RestController
@RequestMapping("/api/products")
@CrossOrigin(
    origins = {"https://app.example.com", "https://staging.example.com"},
    allowedHeaders = "*",
    methods = {RequestMethod.GET, RequestMethod.POST},
    maxAge = 3600  // preflight cache duration in seconds
)
public class ProductController { ... }

// Global CORS config via WebMvcConfigurer — applies to all controllers
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
            .allowedOrigins("https://app.example.com")
            .allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH")
            .allowedHeaders("Authorization", "Content-Type", "X-Tenant-Id")
            .allowCredentials(true)   // allows cookies / auth headers
            .maxAge(3600);
    }
}

When a browser sends a preflight OPTIONS request, Spring’s DispatcherServlet detects it and delegates to PreFlightRequestHandler (via CorsInterceptor). The handler checks the CORS configuration for the requested path, and if the origin, method, and headers are all allowed, it writes the Access-Control-Allow-* response headers and returns 200 with an empty body. The browser then sends the actual request.

The allowCredentials(true) setting requires special attention. When credentials (cookies, Authorization headers) are included in cross-origin requests, the browser requires the server to set Access-Control-Allow-Credentials: true AND to specify an explicit origin (not wildcard *). Setting both allowedOrigins(“*”) and allowCredentials(true) is invalid — Spring throws an error if you attempt this combination.

CORS HeaderPurpose
Access-Control-Allow-OriginSpecifies which origins can access the resource. Either a specific origin or * (not allowed with credentials).
Access-Control-Allow-MethodsHTTP methods the browser may use in the actual request.
Access-Control-Allow-HeadersRequest headers the browser may include in the actual request.
Access-Control-Allow-CredentialsWhether cookies and Authorization headers are included. Requires explicit non-wildcard origin.
Access-Control-Max-AgeHow long (in seconds) the browser caches the preflight result. Reduces preflight overhead.
🎯 Interview Insight:  A common production mistake: configuring CORS in Spring MVC, but also having a Spring Security filter chain. Spring Security’s CORS filter runs before Spring MVC’s CORS handling. If you configure CORS in WebMvcConfigurer but not in Spring Security, the Security filter rejects the preflight request before Spring MVC sees it. The fix: enable CORS in Spring Security via http.cors() and provide a CorsConfigurationSource bean, or use Spring Security 6. x’s cors(withDefaults()), which delegates to Spring MVC’s CorsRegistry configuration automatically.

Conclusion

Spring MVC is far more than a collection of annotations. It is a carefully designed, pluggable architecture built around the DispatcherServlet as the front controller, backed by a chain of strategy objects — HandlerMapping, HandlerAdapter, ViewResolver, HandlerExceptionResolver — each of which can be extended or replaced. Understanding how these components collaborate is what separates candidates who can write Spring MVC code from those who can explain and debug it in production.

Key takeaways from this article:

  • DispatcherServlet orchestrates the full request lifecycle by delegating to pluggable strategies. It never does the actual work itself.
  • HandlerMapping maps URLs to handlers. HandlerAdapter bridges the gap between DispatcherServlet and the many possible handler types. Both are independently extensible.
  • @RequestMapping and its shortcut variants (@GetMapping, @PostMapping, etc.) compose class-level and method-level annotations. The class-level path is a prefix; the method-level path is appended to it.
  • @RequestParam binds query parameters and form data. @PathVariable extracts URI template segments. @RequestBody deserializes the request body. @RequestHeader reads HTTP headers.
  • ViewResolver resolves logical view names to templates for server-side rendering. For @RestController/@ResponseBody methods, the ViewResolver is bypassed entirely — HttpMessageConverters write directly to the response.
  • Content negotiation uses the Accept header to select the best HttpMessageConverter or ViewResolver. The produces attribute on @RequestMapping declares what a method can produce.
  • CORS is handled by CorsProcessor. Configure it globally via WebMvcConfigurer.addCorsMappings() or per-endpoint via @CrossOrigin. When Spring Security is present, CORS must also be configured in the Security filter chain.

Leave a Comment