What is the difference between returning a Callable<T> and a DeferredResult<T> from a Spring MVC controller?
answer
- Callable: Spring runs it on its executor
- DeferredResult: you call setResult from any thread
- DeferredResult = long polling / event-driven bridge
- DeferredResult parks with zero threads busy
- onTimeout / onError / onCompletion callbacks
basics
~20 sWith 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.
solid answer
~40 sBoth put the request into async mode and free the container thread, but they differ in who produces the result. A Callable<T> is *executed by Spring* on its AsyncTaskExecutor: you give Spring the code and it schedules it. A DeferredResult<T> is *not run by Spring at all* — you return an empty holder and later, from whatever thread you like (a message listener, a scheduled callback, an external HTTP client's completion), you call deferredResult.setResult(value) or setErrorResult(ex). That makes DeferredResult the right tool when the result arrives from an event-driven or another-thread source, and it enables patterns like long polling where a request parks with zero threads consumed until data appears. DeferredResult also exposes onTimeout, onError and onCompletion callbacks. Callable is simpler when you just want to move blocking work off the container thread.
code
java · 13 lines// Callable: Spring executes the body on its AsyncTaskExecutor
@GetMapping("/a")
public Callable<String> a() {
return () -> slowCompute(); // Spring owns the thread
}
// DeferredResult: Spring runs nothing; completed elsewhere
@GetMapping("/b")
public DeferredResult<String> b() {
DeferredResult<String> dr = new DeferredResult<>(5000L);
externalService.subscribe(result -> dr.setResult(result)); // any thread
return dr;
}go deeper
Know Callable = Spring runs it; DeferredResult = you complete it later.
Explain the who-runs-the-thread difference and that DeferredResult suits event-driven completion and long polling.
Discuss thread-efficiency: DeferredResult parks with zero threads; callbacks and timeout semantics; late setResult no-op.
Weigh DeferredResult-based long polling / event bridging vs SSE/WebSocket/WebFlux and the operational limits (parked-connection counts, timeouts).
## Same foundation, different control model Both `Callable<T>` and `DeferredResult<T>` trigger Servlet async processing (`request.startAsync()`), release the container thread, keep the connection open, and finish with an async dispatch that runs the produced value through normal return-value handling (message converters, `@ExceptionHandler`, etc.). The difference is **who computes the result and on which thread**. ### Callable<T> — Spring owns the thread You return code. Spring submits that `Callable` to its `AsyncTaskExecutor`; the value returned from `call()` becomes the response. You do not manage threads yourself. Use it to push blocking work off the container pool. ### DeferredResult<T> — you own completion `DeferredResult` is a *holder*. Returning it just parks the request. **Spring executes no code.** Somewhere else — a JMS/Kafka listener, a `CompletableFuture` callback, a WebSocket message, a scheduled task — you keep a reference to that `DeferredResult` and later call: - `setResult(T value)` → completes with a success body, - `setErrorResult(Object error)` → completes with an error (routed through exception handling). That completion can happen on **any thread**, one Spring knows nothing about. When it happens, Spring performs the async dispatch and writes the response. ```java private final Map<String, DeferredResult<Quote>> waiters = new ConcurrentHashMap<>(); @GetMapping("/quote/{id}") public DeferredResult<Quote> quote(@PathVariable String id) { DeferredResult<Quote> dr = new DeferredResult<>(10_000L); // 10s timeout waiters.put(id, dr); dr.onTimeout(() -> dr.setErrorResult(ResponseEntity.status(503).build())); dr.onCompletion(() -> waiters.remove(id)); return dr; // no thread is doing work; request is parked } // Elsewhere, e.g. a message listener thread: @KafkaListener(topics = "quotes") public void onQuote(QuoteEvent e) { DeferredResult<Quote> dr = waiters.get(e.id()); if (dr != null) dr.setResult(e.quote()); // completes the parked request } ``` ## The long-polling / thread-efficiency angle This is the crucial distinction. With `Callable`, a thread from the executor is busy the whole time. With `DeferredResult`, **no thread needs to be running while the request waits** — the request just sits parked until an external event fires `setResult`. That makes `DeferredResult` ideal for **long polling** and for bridging **event-driven / message-driven** systems into HTTP, where you may have thousands of parked requests but very few active threads. ## Callbacks `DeferredResult` offers `onTimeout(Runnable)`, `onError(Consumer<Throwable>)`, and `onCompletion(Runnable)`. If a timeout fires before you set a result, Spring completes the request (by default a 503 via `AsyncRequestTimeoutException`) unless your `onTimeout` sets a result first. Setting a result after completion is a no-op (returns false). ## When to use which - **Callable**: you have blocking work and simply want it off the container thread. Simplest. - **DeferredResult**: the answer comes from another thread / external event, or you want long polling / to hold many connections cheaply. - **Neither** if the work is fast — plain sync is simpler. ## Related types `WebAsyncTask<T>` wraps a `Callable` to add a timeout and a chosen executor. `ListenableFuture`/`CompletableFuture` return types are also supported and behave like a `DeferredResult` fed by the future's completion. Streaming types (`ResponseBodyEmitter`, `SseEmitter`, `StreamingResponseBody`) are separate async response mechanisms.
- Why is DeferredResult better than Callable for long polling?With DeferredResult no thread is running while the request waits — it is simply parked until an external event calls setResult. Callable keeps an executor thread busy for the whole duration, so you'd exhaust the pool with many concurrent long-poll requests.
- What happens if you call setResult after the DeferredResult has already timed out or completed?It is a no-op; setResult returns false because the result is no longer settable. The timeout callback (or default 503) has already completed the request, so the late value is ignored.
saying these in an interview costs you the question
- Saying Spring runs the DeferredResult's work on its executor
- Thinking Callable can be completed from an external thread
- Claiming DeferredResult keeps an executor thread busy while waiting
- Confusing setErrorResult with throwing (it routes through exception handling, not a throw)