skip to content

Interceptors, CORS, Multipart & Async

The concerns that wrap requests rather than handle them: interceptors versus filters, CORS, multipart uploads, async request processing, and streaming responses. Interviewers pick from here when they want practical web knowledge beyond CRUD controllers.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

explore

questions

25

What happens when a Spring MVC controller method returns a Callable<T> instead of a plain value, and why would you do it?

level: juniorimportance: must knowfreq 45%

answer

  1. Callable -> WebAsyncManager -> AsyncTaskExecutor
  2. container thread released, response not committed
  3. async dispatch re-runs return-value handling
  4. default = SimpleAsyncTaskExecutor (unbounded, bad)
  5. configureAsyncSupport to set real executor

basics

~10 s

Spring runs the Callable on a separate thread pool and frees the servlet (Tomcat) thread immediately, so it can handle other requests. When the Callable finishes, Spring resumes and writes the response.

solid answer

~40 s

Returning a Callable<T> switches the request into asynchronous processing. Spring's WebAsyncManager submits the Callable to a TaskExecutor (an AsyncTaskExecutor) and returns the servlet container thread to Tomcat's pool right away, without committing the response. The container thread is now free to serve other requests while your slow work runs on the executor thread. When the Callable returns a value, Spring performs an async dispatch back to the container, and the return value goes through the normal handler-return-value machinery (message converters, view resolution, @ResponseBody) exactly as if it had been returned synchronously. The client sees no difference — it just waits for the response. The point is to avoid tying up the limited container thread pool during long or I/O-bound work.

code

java · 13 lines
java
@RestController
class ReportController {

  @GetMapping("/report")
  public Callable<Report> report() {
    // Returns immediately; container thread is freed.
    // The lambda runs on Spring's AsyncTaskExecutor.
    return () -> {
      Thread.sleep(3000);            // simulate slow work
      return new Report("quarterly"); // becomes the response body
    };
  }
}

go deeper

for a junior

Know that returning Callable frees the servlet thread and the work runs elsewhere; the client sees a normal response.

for a middle

Explain WebAsyncManager, the async dispatch, and the default SimpleAsyncTaskExecutor gotcha plus how to configure a real one.

for a senior

Discuss that it decouples pools without removing blocking, and how return-value handling re-runs on async dispatch.

for a principal

Frame when Callable async is worth it vs plain sync vs WebFlux, and executor sizing/back-pressure implications.

## The problem it solves A servlet container like Tomcat serves each incoming HTTP request on a thread from a fixed-size pool (e.g. 200 threads). If a controller does slow work (a slow downstream HTTP call, a long computation), that container thread is blocked and unavailable to any other request for the whole duration. Under load the pool exhausts and new requests queue or are rejected. **Asynchronous request processing** lets you hand the slow work to a *different* thread pool and give the container thread back immediately, so the container stays responsive. ## What `Callable<T>` does, step by step `java.util.concurrent.Callable<T>` is just `call()` returning a `T`. When your `@RequestMapping`/`@GetMapping` method returns one: 1. Spring's `RequestMappingHandlerAdapter` sees the return type and, via `CallableMethodReturnValueHandler`, starts async processing. 2. `WebAsyncManager` (obtained internally through `WebAsyncUtils.getAsyncManager(request)`) submits your `Callable` to an `AsyncTaskExecutor`. 3. The original **servlet container thread is released back to Tomcat** — but the HTTP response is *not* committed; the connection stays open. This uses the Servlet 3.0 `request.startAsync()` mechanism under the hood. 4. Your `Callable.call()` runs on an executor thread. Whatever it returns becomes the *concurrent result*. 5. Spring issues an **async dispatch** (`DispatcherType.ASYNC`) — the container re-dispatches the request back into the `DispatcherServlet`, which now picks up the concurrent result and runs it through the *normal* return-value handling: `@ResponseBody` + `HttpMessageConverter`, or view resolution, exception handling via `@ExceptionHandler`, etc. So `return () -> slowThing();` behaves for the client exactly like `return slowThing();` — same status, same body — but without holding the container thread during `slowThing()`. ## The default executor gotcha If you don't configure anything, Spring uses a **`SimpleAsyncTaskExecutor`**, which creates a **brand-new thread for every task and does not pool or cap them**. That is fine for a demo but dangerous in production (unbounded thread creation). You should register a real, bounded executor: ```java @Configuration public class AsyncConfig implements WebMvcConfigurer { @Override public void configureAsyncSupport(AsyncSupportConfigurer c) { ThreadPoolTaskExecutor ex = new ThreadPoolTaskExecutor(); ex.setCorePoolSize(8); ex.setMaxPoolSize(32); ex.setQueueCapacity(100); ex.setThreadNamePrefix("mvc-async-"); ex.initialize(); c.setTaskExecutor(ex); c.setDefaultTimeout(30_000); } } ``` ## Key nuance: it does not reduce total threads The work still consumes a thread — just from a *different* pool. Async MVC with `Callable` **decouples** the container pool from the work pool; it does not make blocking work non-blocking. The real thread-count win comes with `DeferredResult` when *no* thread waits during idle time (long polling). For genuinely non-blocking I/O you'd use WebFlux (a sibling topic). ## When to use it - Long-running or I/O-bound handlers where you want to protect the container thread pool. - You want a specific, bounded, named executor for a class of work. - You want per-request timeouts (`WebAsyncTask`) on that work. If the work is trivial and fast, plain synchronous returns are simpler and better.

  • Does returning a Callable make blocking work non-blocking?
    No. The blocking work still occupies a thread, just one from the async TaskExecutor instead of the container pool. It decouples the pools but doesn't change the blocking nature. True non-blocking I/O requires WebFlux.
  • What executor runs the Callable by default and why is that a problem?
    SimpleAsyncTaskExecutor, which spawns a new unbounded thread per task with no pooling or cap. Under load this can exhaust memory/threads, so you should register a bounded ThreadPoolTaskExecutor via WebMvcConfigurer.configureAsyncSupport.

saying these in an interview costs you the question

  • Claiming Callable makes the handler non-blocking / reactive
  • Thinking the same servlet thread waits for the Callable to finish
  • Assuming the default executor is a bounded thread pool
  • Believing the response is written twice or the method runs twice

context

open as a page

What is CORS, and how do you enable cross-origin requests for a single Spring MVC controller or handler method?

level: juniorimportance: must knowfreq 75%

basics

~20 s

CORS (Cross-Origin Resource Sharing) is a browser rule that blocks JavaScript from calling a different origin unless the server allows it. In Spring you allow it by adding @CrossOrigin on a controller class or handler method.

open as a page

What is the difference between a Servlet Filter and a Spring HandlerInterceptor?

level: juniorimportance: must knowfreq 72%

basics

~10 s

A Filter runs in the Servlet container around every request, before Spring even sees it. A HandlerInterceptor runs inside Spring MVC's DispatcherServlet, only for controller requests, and knows which controller (handler) will run.

open as a page

How do you receive an uploaded file in a Spring MVC controller, and what does MultipartFile give you?

level: juniorimportance: must knowfreq 60%

basics

~10 s

Add a handler method parameter of type MultipartFile annotated with @RequestParam matching the form field name. Spring binds the uploaded part to it; then call getOriginalFilename(), getBytes()/getInputStream(), getSize(), and transferTo() to save it.

open as a page

What is SseEmitter in Spring MVC, and how do you use it to stream Server-Sent Events to a browser?

level: juniorimportance: must knowfreq 55%

basics

~20 s

SseEmitter is a Spring MVC return type that lets a controller push multiple messages to the client over one open HTTP connection as Server-Sent Events (text/event-stream). You return it immediately, then call emitter.send(...) for each event and emitter.complete() when done.

open as a page

What is the difference between returning a Callable<T> and a DeferredResult<T> from a Spring MVC controller?

level: middleimportance: must knowfreq 40%

basics

~20 s

With Callable, Spring runs your code on its own thread pool. With DeferredResult, Spring runs nothing — you complete it later from any thread by calling setResult(...), which is ideal for external events or long polling.

open as a page

How do you configure CORS globally in Spring MVC, and when would you choose global config over @CrossOrigin?

level: middleimportance: must knowfreq 70%

basics

~10 s

Implement WebMvcConfigurer and override addCorsMappings(CorsRegistry). Use registry.addMapping("/api/**") then chain allowedOrigins, allowedMethods, allowedHeaders, allowCredentials, maxAge. Prefer it over @CrossOrigin when you want one central, consistent policy across many controllers.

open as a page

Explain the three HandlerInterceptor methods — preHandle, postHandle, afterCompletion — and when each is (and isn't) called.

level: middleimportance: must knowfreq 58%

basics

~20 s

preHandle runs before the controller and can abort by returning false. postHandle runs after the controller but before the view renders (skipped if the controller throws). afterCompletion runs after the response is complete, even on error, and receives the exception.

open as a page

Compare ResponseBodyEmitter, SseEmitter, and StreamingResponseBody. When would you choose each?

level: middleimportance: must knowfreq 50%

basics

~20 s

All three stream a response incrementally. ResponseBodyEmitter pushes objects through message converters over time; SseEmitter is a subclass that formats them as Server-Sent Events (text/event-stream). StreamingResponseBody gives you the raw OutputStream to write bytes yourself — ideal for large files or non-object data.

open as a page

What is the interaction between allowCredentials(true) and allowedOrigins("*"), and how do you allow credentials with multiple/dynamic origins?

level: seniorimportance: must knowfreq 62%

basics

~20 s

You cannot combine allowCredentials(true) with allowedOrigins("*") — the browser forbids credentials when Access-Control-Allow-Origin is the wildcard, and recent Spring throws at startup. Use allowedOriginPatterns(...) (or list exact origins) so Spring echoes the specific origin back.

open as a page

What does WebAsyncTask<T> add over a plain Callable<T>, and how do async request timeouts work in Spring MVC?

level: middleimportance: should knowfreq 25%

basics

~10 s

WebAsyncTask wraps a Callable so you can set a per-request timeout and pick a specific executor, plus timeout/error callbacks. Timeouts otherwise come from a global setting and produce a 503 by default.

open as a page

Explain the CORS preflight OPTIONS flow: when the browser sends it, what headers are exchanged, and how Spring handles it.

level: middleimportance: should knowfreq 60%

basics

~20 s

For non-simple requests (e.g. PUT/DELETE, JSON content type, or custom headers), the browser first sends an OPTIONS 'preflight' asking permission via Access-Control-Request-Method/Headers. The server answers with Access-Control-Allow-* headers. Spring answers this automatically from your CORS config — your controller isn't invoked.

open as a page

How do you register and order Servlet Filters and HandlerInterceptors in a Spring Boot app, and what is OncePerRequestFilter for?

level: middleimportance: should knowfreq 46%

basics

~20 s

Register interceptors by implementing WebMvcConfigurer.addInterceptors and calling registry.addInterceptor(...); order = registration order. Register filters as beans (Spring Boot auto-registers them) or via FilterRegistrationBean to control order and URL patterns. OncePerRequestFilter guarantees the filter runs once per request, even across forwards/async dispatches.

open as a page

What is the difference between @RequestParam and @RequestPart for multipart requests?

level: middleimportance: should knowfreq 45%

basics

~20 s

@RequestParam does simple type conversion and works for text fields and MultipartFile. @RequestPart considers each part's Content-Type and uses HttpMessageConverters — so it can deserialize a JSON part into an object, like @RequestBody per part.

open as a page

How does Spring detect and parse multipart requests, and what is StandardServletMultipartResolver?

level: middleimportance: should knowfreq 35%

basics

~10 s

The DispatcherServlet asks a MultipartResolver bean (named multipartResolver) whether the request is multipart; if so it wraps it as a MultipartHttpServletRequest. Spring Boot auto-configures StandardServletMultipartResolver, which delegates parsing to the Servlet container.

open as a page

How do you build a structured SSE event with id, event name, and retry using SseEmitter, and how does the browser use those fields?

level: middleimportance: should knowfreq 33%

basics

~20 s

Use SseEmitter.event() to get an SseEventBuilder, then set .id(...), .name(...) (the event type), .reconnectTime(...) (retry) and .data(...), and pass it to emitter.send(). The browser's EventSource uses id for Last-Event-ID on reconnect, name to route to addEventListener, and retry as the reconnect delay.

open as a page

Explain the WebAsyncManager mechanism: how is the servlet thread released and re-attached, and what happens to thread-locals like the SecurityContext during async MVC?

level: seniorimportance: should knowfreq 30%

basics

~20 s

WebAsyncManager calls request.startAsync() to free the container thread, runs the work on an executor, then triggers an async dispatch to resume. Thread-locals (SecurityContext, request scope) don't automatically move to the executor thread — Spring Security propagates them for Callable via a special filter, but you must handle it yourself for DeferredResult.

open as a page

When would you choose a Filter over a HandlerInterceptor (and vice versa)? Walk through the full execution flow of a request through both.

level: seniorimportance: should knowfreq 40%

basics

~20 s

Use a Filter for low-level, framework-agnostic work that must wrap the whole request or touch raw request/response bytes (logging with body caching, compression, CORS, security). Use an interceptor when you need the matched Spring handler or the ModelAndView. Flow: filters wrap interceptors wrap the handler.

open as a page

How do you enforce upload size limits in Spring, and how do you handle the resulting error?

level: seniorimportance: should knowfreq 45%

basics

~10 s

Set spring.servlet.multipart.max-file-size and max-request-size (defaults 1MB / 10MB). Exceeding them throws MaxUploadSizeExceededException, which you catch in a @ControllerAdvice/@ExceptionHandler to return a clean 413/400 response.

open as a page

Explain the SseEmitter lifecycle: completion, timeout, and error callbacks, and how you manage disconnects and long-lived emitters.

level: seniorimportance: should knowfreq 42%

basics

~20 s

You register onCompletion, onTimeout, and onError callbacks. onTimeout and onError fire when the connection times out or an I/O error occurs (e.g. client disconnect); in both you should call complete()/completeWithError(). onCompletion always fires at the end — use it to remove the emitter from your registry.

open as a page

As an architect, when would you choose async Spring MVC (Callable/DeferredResult) over plain blocking MVC, and how does it differ from choosing WebFlux? How do you size and isolate the executor?

level: principalimportance: should knowfreq 30%

basics

~20 s

Use async MVC to protect the container thread pool for long or event-driven work, or for long polling with DeferredResult. It still uses blocking threads — it just moves them off the container pool. WebFlux is different: true non-blocking I/O with few threads, but the whole stack must be reactive. Size executors as bounded, isolated pools sized to the work.

open as a page

How does CORS interact with Spring Security's filter chain, and what is the correct way to configure CORS when Spring Security is present?

level: principalimportance: should knowfreq 55%

basics

~20 s

Security filters run before Spring MVC, so an unauthenticated preflight OPTIONS can be blocked before MVC's CORS handling. Enable Security's own CORS with http.cors() and provide a CorsConfigurationSource bean; then Security applies CORS early and lets preflight through.

open as a page

In the servlet stack, how does SSE/emitter streaming interact with the threading model, and when does it stop scaling? How does WebFlux differ?

level: principalimportance: should knowfreq 30%

basics

~20 s

Spring MVC frees the original request thread via async processing, but each open SSE connection still ties up a socket and needs a container thread whenever it writes. On a thread-per-connection container, thousands of concurrent long-lived streams exhaust the thread pool. WebFlux uses non-blocking I/O with a small event-loop, so it holds many idle streams cheaply.

open as a page

How do HandlerInterceptors and Filters behave with async request processing (Callable/DeferredResult), and what subtle pitfalls arise around postHandle, thread-locals, and dispatch types?

level: principalimportance: nice to knowfreq 24%

basics

~20 s

When a controller returns a Callable or DeferredResult, Spring releases the request thread. For interceptors that means postHandle/afterCompletion are deferred until the async result is re-dispatched; AsyncHandlerInterceptor.afterConcurrentHandlingStarted is called instead on the first pass. Thread-locals set on the original thread don't exist on the async worker thread, so filter/interceptor state must be propagated explicitly.

open as a page

Where do multipart temp files live, how are they cleaned up, and how would you design a memory-safe path for very large uploads?

level: principalimportance: nice to knowfreq 28%

basics

~20 s

Parts above file-size-threshold spool to the temp dir (spring.servlet.multipart.location, default container temp). The container deletes them when the request ends. For huge uploads, stream via getInputStream()/transferTo() to storage instead of getBytes(), and cap sizes to bound resources.

open as a page