How do HandlerFilterFunctions work in Gateway Server MVC, and how do before/after/filter differ?
answer
- HandlerFilterFunction = around(request, next)
- before -> request transform; after -> response transform
- filter(...) = short-circuit/retry/circuit-breaker/lb
- BeforeFilterFunctions / AfterFilterFunctions factories
- Scoped to routes declared before them
basics
~10 sFilters are HandlerFilterFunctions that wrap the handler. before-filters transform the ServerRequest, after-filters transform the ServerResponse, and a full filter wraps both sides and can short-circuit. Add them from factories like BeforeFilterFunctions and AfterFilterFunctions.
solid answer
~40 sA filter is a `HandlerFilterFunction<ServerResponse, ServerResponse>` from Spring MVC's functional web — it receives the `ServerRequest` and the next `HandlerFunction` and returns a `ServerResponse`, so it can act before, after, or around the handler. Gateway provides factories: `BeforeFilterFunctions` (e.g. `stripPrefix`, `setPath`, `rewritePath`, `addRequestHeader`, `setRequestHost`) produce request-side transforms you attach with `.before(...)`; `AfterFilterFunctions` (e.g. `addResponseHeader`, `setStatus`, `rewriteResponseHeader`, `removeResponseHeader`) produce response-side transforms attached with `.after(...)`. `.filter(...)` takes a full `HandlerFilterFunction` for around-behavior and short-circuiting — this is how `CircuitBreakerFilterFunctions.circuitBreaker(...)`, `RetryFilterFunctions`, `LoadBalancerFilterFunctions.lb()`, rate limiting, and token relay are wired. Under the hood `.before`/`.after` are convenience adapters that produce full HandlerFilterFunctions; execution order follows the chain, with before-filters running in declaration order and after-filters unwinding around the handler.
code
java · 24 linesimport org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.function.RouterFunction;
import org.springframework.web.servlet.function.ServerResponse;
import static org.springframework.cloud.gateway.server.mvc.filter.AfterFilterFunctions.addResponseHeader;
import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.*;
import static org.springframework.cloud.gateway.server.mvc.filter.CircuitBreakerFilterFunctions.circuitBreaker;
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
import static org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequestPredicates.path;
@org.springframework.context.annotation.Configuration
class FilteredRoutes {
@Bean
RouterFunction<ServerResponse> orders() {
return GatewayRouterFunctions.route("orders")
.route(path("/orders/**"), http("https://orders:8080"))
.before(stripPrefix(1)) // request-side
.before(addRequestHeader("X-Source", "gw")) // request-side
.filter(circuitBreaker("ordersCb", "/fallback/orders")) // around + short-circuit
.after(addResponseHeader("X-Cache", "MISS")) // response-side
.build();
}
}go deeper
Knows filters modify request/response; may not distinguish before/after/filter.
Uses BeforeFilterFunctions/AfterFilterFunctions correctly for header/path changes.
Explains the around semantics, short-circuiting, and where circuit breaker / lb / rate-limit filters plug in.
Reasons about filter ordering, global vs per-route, servlet-filter interplay, and short-circuit effects on the after-chain.
**The type.** In Spring MVC's functional web, `org.springframework.web.servlet.function.HandlerFilterFunction<T, R>` has the signature `R filter(ServerRequest request, HandlerFunction<T> next)`. For the gateway it is parameterized `<ServerResponse, ServerResponse>`. Because a filter is handed **both** the request and the `next` handler, it is fundamentally an **around** interceptor: it may modify the request, call `next.handle(request)`, modify or replace the response, or **not call next at all** (short-circuit, e.g. rate-limit rejection or an open circuit breaker). **Three attachment styles on the route builder:** - **`.before(Function<ServerRequest, ServerRequest>)`** — a request-side transform. Gateway adapts it into a HandlerFilterFunction that applies your function, then calls next. Source: `org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions` — e.g. `stripPrefix(n)`, `setPath(template)`, `rewritePath(regex, replacement)`, `addRequestHeader(name, value)`, `addRequestParameter`, `setRequestHost`, `preserveHostHeader`, `removeRequestHeader`. - **`.after(BiFunction<ServerRequest, ServerResponse, ServerResponse>)`** — a response-side transform, applied after next returns. Source: `AfterFilterFunctions` — e.g. `addResponseHeader`, `setResponseHeader`, `rewriteResponseHeader`, `removeResponseHeader`, `setStatus(...)`, `dedupeResponseHeader`. - **`.filter(HandlerFilterFunction<ServerResponse, ServerResponse>)`** — a full around filter. This is required for cross-cutting infra filters that need to wrap the whole exchange or short-circuit: `CircuitBreakerFilterFunctions.circuitBreaker(config)`, `RetryFilterFunctions.retry(n)`, `LoadBalancerFilterFunctions.lb()` (resolves `lb://` to an instance URI via Spring Cloud LoadBalancer), `TokenRelayFilterFunctions.tokenRelay()` (OAuth2 token propagation), and rate limiting (e.g. Bucket4j-based filter functions). **Execution order.** Conceptually filters form nested wrappers around the terminal `http()` handler. Before-side logic runs top-down in declaration order; the handler proxies upstream; after-side logic unwinds bottom-up. A `.filter(...)` that surrounds a nested router applies to all routes it wraps — this is the same scoping gotcha as the DSL: filters attach to the routes declared before them on that builder. **Global filters.** Beyond per-route filters you can register filters that apply to all routes (via configuration / `FilterFunctions` beans or `spring.cloud.gateway.mvc.default-filters` style config), useful for org-wide concerns like tracing or security headers. **Servlet interplay.** Because this is the servlet stack, ordinary servlet `Filter`s and `HandlerInterceptor`s still run around the DispatcherServlet — you get two layers (container/servlet filters and gateway HandlerFilterFunctions). Prefer HandlerFilterFunctions for route-scoped, gateway-aware logic; servlet filters for truly global container concerns. **Gotchas.** - Choosing `.before`/`.after` when you actually need to short-circuit or retry — those require the full `.filter(...)` form. - Header mutation timing: request-header filters must be `.before` (they run before proxying); response-header filters must be `.after`. - `stripPrefix`/`rewritePath` order relative to other before-filters changes the path the upstream sees. - Forgetting that a short-circuiting filter that never calls `next` means downstream after-filters may not run as expected.
- You need to reject a request with 429 before it ever hits the upstream. Which attachment style do you use and why?A full .filter(...) (a HandlerFilterFunction) — because only an around filter can decide not to call next.handle() and instead return its own ServerResponse (429). .before can only transform the request; it always proceeds to the handler.
- How does the gateway resolve an lb://order-service URI in the MVC variant?You use the no-arg http() handler plus LoadBalancerFilterFunctions.lb() as a filter; lb() consults Spring Cloud LoadBalancer to pick an instance and sets the concrete URI as a request attribute that http() then proxies to.
saying these in an interview costs you the question
- Believing .before can short-circuit or return a response
- Using .after for request headers or .before for response headers
- Assuming filters are global by default rather than scoped to preceding routes