How does a Servlet Filter short-circuit the chain, and what are correct practices when doing so?
answer
- Omit chain.doFilter = short-circuit (handle here, stop)
- Always return after writing the terminal response
- Don't delegate after the response is committed (IllegalStateException)
- Post-chain code runs only if you delegated → use finally for cleanup
- Set CORS/security headers before cutting the chain
basics
~20 sA filter short-circuits by not calling chain.doFilter and instead producing the response itself — for example writing a 401 or sending a redirect. Then no later filter or the servlet runs. You must fully complete the response and not commit it twice.
solid answer
~50 sA filter ends the chain early by simply not calling chain.doFilter(request, response). Instead it writes the terminal response itself: set the status (e.g. response.setStatus or sendError), optionally a body or a sendRedirect, then return. The container treats the absence of a downstream call as "handled here." Correct practice: do not call chain.doFilter after you have already committed a response, or you will get IllegalStateException or a corrupt/double response. Decide early — check the condition before any wrapping or expensive work. Be careful that filters running *after* you in a normal flow won't get to set headers they expect to; if those are required (CORS, security headers), apply them before short-circuiting or in the short-circuiting filter itself. Also remember the post-processing code after chain.doFilter only runs if you actually delegated, so cleanup logic must handle both the delegated and short-circuited paths. This is exactly how auth, rate-limit, and CORS-preflight filters work.
go deeper
Knows that omitting chain.doFilter stops the request and that the filter then writes the response (e.g. 401).
Can implement a short-circuit correctly: write status/redirect, return, and avoid calling chain.doFilter afterward.
Understands committed-response constraints, the post-chain-only execution of cleanup, header-ordering pitfalls (CORS/security), and uses finally for cleanup across both paths.
Designs gate filters (auth, rate-limit, CORS) with correct ordering and short-circuit contracts across the stack, and accounts for committed-buffer, error-page, and wrapper interactions.
### Recap of the mechanism Servlet Filters implement **Chain of Responsibility**. Each filter's `doFilter(request, response, chain)` may call `chain.doFilter(request, response)` to invoke the **next** handler (filter or servlet). The call is **synchronous and nested**: everything downstream runs *inside* that call, and your code after it runs on the unwind. ### What "short-circuit" means To **short-circuit** is to **end the request at this filter** and not let any downstream handler run. You do it by **omitting the `chain.doFilter` call** and instead producing the response yourself. The container sees no downstream invocation and returns the response you built. Typical short-circuit triggers: - **Authentication**: no/invalid credentials → `response.sendError(401)` and return. - **Authorization**: authenticated but not permitted → `403`. - **Rate limiting**: over quota → `429`. - **CORS preflight**: an `OPTIONS` preflight request → set CORS headers, `200`, and return without hitting the servlet. - **Redirect**: e.g. force HTTPS → `response.sendRedirect(httpsUrl)` and return. ### Key terms - **Committed response**: once the first bytes (status line + headers) are flushed to the client, the response is "committed." After that you **cannot** change the status or headers, and you cannot meaningfully restart processing. - **`sendError(code)` / `sendRedirect(url)`**: convenience methods that set status (and for redirect, the `Location` header) and commit the response; the container may render an error page for `sendError`. - **`IllegalStateException`**: thrown if you try to write/forward/redirect after the response is committed, or mix incompatible output (e.g. getWriter after getOutputStream). ### Correct practices when short-circuiting 1. **Decide early.** Evaluate the short-circuit condition *before* doing expensive work, wrapping the request/response, or delegating. Once you call `chain.doFilter`, downstream may commit the response and you lose control. 2. **Never delegate after committing.** Do not call `chain.doFilter` once you (or downstream) committed the response — that risks `IllegalStateException` or a garbled double response. Conversely, after you short-circuit, **return immediately**; do not fall through into `chain.doFilter`. 3. **Set required headers before/with the short-circuit.** Headers that downstream filters would normally add (CORS, security headers like `X-Content-Type-Options`) will **not** be added if you cut the chain — so the short-circuiting filter must add the ones that still apply. 4. **Mind the post-processing branch.** Code after `chain.doFilter` runs **only** on the delegated path. If you have cleanup (close resources, MDC clear), put it in a `finally` so it runs whether you delegated or short-circuited, or duplicate it on the short-circuit path. 5. **Use the right status semantics.** `sendError` vs. `setStatus`+manual body differ: `sendError` lets the container render an error page and resets the buffer; `setStatus` keeps your own body. Choose based on whether you want the container's error handling. 6. **Be idempotent about wrapping.** If earlier filters wrapped the request/response, ensure your short-circuit writes to the correct (wrapped) response object so headers/body land where the client reads them. ### Common bugs - **Falling through**: writing an error *and then still calling* `chain.doFilter`, producing two responses. - **Committing too late**: doing work, partially writing, then trying to redirect → `IllegalStateException`. - **Missing headers on the rejection**: e.g. a CORS preflight rejected by auth before CORS headers were set, so the browser reports a CORS error instead of a clean 401. - **Leaking auth**: forgetting to `return` after `sendError`, so execution continues into the protected chain. ### Why this is the heart of the pattern Short-circuiting is Chain of Responsibility's defining behavior: a handler that **handles and stops** rather than delegating. Filters give you fine control over exactly when to stop, which is what makes them the right tool for security and quota gates.
- Why might an auth filter rejecting a request cause a browser CORS error instead of a clean 401?If the auth filter short-circuits before the CORS filter adds Access-Control-Allow-Origin, the browser sees a cross-origin response without CORS headers and reports it as a CORS failure. Fix: order CORS first, or have the auth filter emit the CORS headers on its rejection.
- Where should you put resource cleanup so it runs on both delegated and short-circuited paths?In a finally block around the logic, since code placed after chain.doFilter only executes when you actually delegated.
saying these in an interview costs you the question
- Calling chain.doFilter after already writing an error/redirect
- Forgetting return after sendError, leaking into protected code
- Assuming cleanup after chain.doFilter always runs (it doesn't on short-circuit)
- Thinking you can change status/headers after the response is committed