Explain the three HandlerInterceptor methods — preHandle, postHandle, afterCompletion — and when each is (and isn't) called.
answer
- pre=before, return false aborts
- post=after controller, before view, skipped on exception
- afterCompletion=always (if pre true), gets Exception, cleanup
- post/after run in reverse order
- false preHandle -> only prior interceptors' afterCompletion
basics
~20 spreHandle 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.
solid answer
~40 sHandlerInterceptor has three callbacks. preHandle(req, res, handler) runs before the controller; returning true continues, false aborts the request (you must write the response yourself). postHandle(req, res, handler, ModelAndView) runs after the controller returns successfully but before the view is rendered, so you can tweak the model — it is NOT called if the handler threw. afterCompletion(req, res, handler, Exception ex) runs after rendering is complete, always fires if preHandle returned true, and receives any exception (or null) — ideal for cleanup like clearing MDC or timers. With multiple interceptors, preHandle runs in registration order, while postHandle and afterCompletion run in reverse order, forming a nested wrap. If one interceptor's preHandle returns false, afterCompletion is only called on the interceptors whose preHandle already succeeded.
code
java · 27 linespublic class TimingInterceptor implements HandlerInterceptor {
private static final String START = TimingInterceptor.class.getName() + ".start";
@Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) {
req.setAttribute(START, System.nanoTime());
return true; // continue; return false to abort (must write response yourself)
}
@Override
public void postHandle(HttpServletRequest req, HttpServletResponse res,
Object handler, ModelAndView mav) {
// Runs only on success, before view render. Skipped if handler threw.
if (mav != null) mav.addObject("generatedAt", Instant.now());
}
@Override
public void afterCompletion(HttpServletRequest req, HttpServletResponse res,
Object handler, Exception ex) {
// Always runs (preHandle returned true), even on exception. Ideal for cleanup.
Long start = (Long) req.getAttribute(START);
if (start != null) {
long ms = (System.nanoTime() - start) / 1_000_000;
log.info("{} took {} ms, ex={}", req.getRequestURI(), ms, ex);
}
}
}go deeper
Name the three methods and the before/after ordering.
Explain the false-abort semantics, the postHandle-skipped-on-exception rule, and afterCompletion's exception argument.
Add the reverse-order execution and the partial-unwind cleanup rule when preHandle returns false.
Connects afterCompletion's guarantees to reliable resource/thread-local cleanup and the async deferral via AsyncHandlerInterceptor.
## The interface ```java public interface HandlerInterceptor { default boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) { return true; } default void postHandle(HttpServletRequest req, HttpServletResponse res, Object handler, ModelAndView mav) {} default void afterCompletion(HttpServletRequest req, HttpServletResponse res, Object handler, Exception ex) {} } ``` All three are `default`, so you override only what you need. `handler` is the matched handler — usually a `HandlerMethod` you can `instanceof`-check to read controller annotations. ## preHandle - Runs **before** the controller method. - Returns `boolean`: `true` = continue the chain; `false` = **stop**. When you return `false`, Spring does NOT call the handler, does NOT call `postHandle`, and does NOT render a view — you are responsible for having already written the response (e.g. `res.sendError(401)` or a redirect). If you return `false` without writing anything, the client gets an empty 200. - Common uses: authentication/authorization checks, request validation, starting a timer, populating MDC/thread-locals. ## postHandle - Runs **after** the controller returns, **before** the view is rendered. - Receives the `ModelAndView` (may be `null`, e.g. for `@ResponseBody` / REST or when the response is already committed). - **Not called if the handler threw an exception** — control jumps past it to exception handling. - Practical limitation: for REST (`@ResponseBody`), the body is already serialized by an `HttpMessageConverter`; mutating the model here does nothing. It is mainly useful with view technologies (Thymeleaf/JSP) to add common model attributes. ## afterCompletion - Runs **after the complete request has finished**, i.e. after the view is rendered (or after the exception path). - **Always called if that interceptor's `preHandle` returned `true`**, regardless of success or failure. - Receives the `Exception ex` that occurred (or `null`). Note it receives the exception even if a `@ControllerAdvice`/`HandlerExceptionResolver` handled it — so it is a reliable place for cleanup: clearing thread-locals/MDC, stopping timers, releasing resources. ## Ordering with multiple interceptors Interceptors nest like layers of an onion: ``` preHandle A -> preHandle B -> [handler] -> postHandle B -> postHandle A -> [view] -> afterCompletion B -> afterCompletion A ``` - `preHandle`: **registration order**. - `postHandle` and `afterCompletion`: **reverse** registration order. ### The abort/cleanup rule (important edge case) If `preHandle` of interceptor B returns `false`: - No further interceptors' preHandle run; handler and postHandle are skipped. - `afterCompletion` is called **only for the interceptors whose preHandle already returned true** (here, A), and B's own afterCompletion is **not** called. Spring tracks how far the chain got and unwinds exactly that far. This is why cleanup in afterCompletion is safe — it mirrors the successful preHandle calls. ## AsyncHandlerInterceptor For async controllers (returning `Callable`, `DeferredResult`), implement `AsyncHandlerInterceptor` and its extra `afterConcurrentHandlingStarted` — see the async follow-up. In the async case, when concurrent handling starts, `postHandle`/`afterCompletion` are deferred until the async result is dispatched.
- If preHandle returns false, what must you do and what happens to the response?You must write the response yourself (e.g. res.sendError(HttpServletResponse.SC_UNAUTHORIZED) or a redirect). Spring will not invoke the handler, postHandle, or view rendering. If you return false without writing anything, the client receives an empty 200 OK, which is a common bug.
- A controller throws an exception. Which of the three methods run?preHandle already ran (that's how we reached the handler). postHandle is skipped. afterCompletion runs with the thrown Exception passed as its ex argument — so cleanup and error logging belong there, not in postHandle.
saying these in an interview costs you the question
- Saying postHandle runs even when the controller throws
- Thinking afterCompletion is skipped on exceptions
- Claiming returning false auto-sends a 403/401 for you
- Believing postHandle can rewrite a JSON @ResponseBody
- Assuming all three methods run in the same order for multiple interceptors