How do you define a route programmatically in Gateway Server MVC using the RouterFunctions DSL?
answer
- GatewayRouterFunctions.route(id) -> builder
- route(predicate, http(uri))
- before/after/filter then build()
- Predicates from GatewayRequestPredicates, composable
- http() no-arg reads URI attribute (lb://)
basics
~10 sDeclare a RouterFunction<ServerResponse> bean built with GatewayRouterFunctions.route(id), pairing a predicate (like path("/api/**")) with the http() handler that names the upstream URI, then call build().
solid answer
~30 sYou expose a `RouterFunction<ServerResponse>` bean created via `GatewayRouterFunctions.route(id)`, which returns a builder. You attach one or more route definitions with `.route(predicate, handlerFunction)`, where the predicate comes from `GatewayRequestPredicates` (e.g. `path("/api/**")`, `method(GET)`, `host(...)`, combinable with `.and()`/`.or()`/`.negate()`), and the handler is `HandlerFunctions.http(uri)` which proxies upstream via RestClient. Filters are chained with `.before(...)`, `.after(...)`, and `.filter(...)` and apply to the routes defined before them on that builder. Finally `.build()` returns the `RouterFunction`. This mirrors Spring MVC's functional web endpoints (`org.springframework.web.servlet.function.RouterFunctions`) but adds gateway-specific predicate/handler/filter factories. You can register multiple RouterFunction beans; Spring composes them, and order/specificity determines which route wins.
code
java · 22 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.stripPrefix;
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
import static org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequestPredicates.path;
import static org.springframework.web.bind.annotation.RequestMethod.*;
@org.springframework.context.annotation.Configuration
class Routes {
@Bean
RouterFunction<ServerResponse> catalog() {
return GatewayRouterFunctions.route("catalog")
.route(path("/catalog/**"), http("https://catalog:8080"))
.before(stripPrefix(1)) // /catalog/x -> /x upstream
.after(addResponseHeader("X-Served-By", "gateway-mvc"))
.build();
}
}go deeper
Can name RouterFunction bean + predicate + http() at a high level.
Writes the DSL correctly, composes predicates, adds before/after filters, calls build().
Explains scoping of filters, first-match ordering, and config-vs-DSL tradeoffs.
Reasons about route organization at scale, lb:// attribute-driven handlers, and shadowing risks.
**Foundation.** Gateway Server MVC builds on Spring MVC's **functional web** API (`org.springframework.web.servlet.function.RouterFunctions`, `RequestPredicate`, `HandlerFunction`, `ServerRequest`, `ServerResponse`). Instead of `@RequestMapping` controllers, you compose routes as data. Gateway adds a specialized entry point, `org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions`, plus predicate and handler factories tuned for proxying. **The builder flow:** 1. `GatewayRouterFunctions.route(String routeId)` — starts a builder tagged with an id used in logs/metrics/actuator. 2. `.route(RequestPredicate predicate, HandlerFunction<ServerResponse> handler)` — registers one route. Multiple `.route(...)` calls add more routes to the same builder. 3. `.before(...)`, `.after(...)`, `.filter(...)` — attach filters. **Scoping gotcha:** these apply to the routes already declared on the builder up to that point (they wrap that nested router), so ordering of calls matters. For clarity many teams give each route its own builder/bean. 4. `.build()` — returns `RouterFunction<ServerResponse>`, which you return as a `@Bean`. **Predicates** — from `org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequestPredicates`: `path(...)`, `method(...)`, `host(...)`, `header(...)`, `query(...)`, `cookie(...)`, `after/before/between(...)` (time), `weight(...)`, etc. Because they are `RequestPredicate`s you can compose: `path("/api/**").and(method(HttpMethod.GET))`, `.or(...)`, `.negate()`. **Handler** — `HandlerFunctions.http(URI)` or `http(String)` returns the proxying `HandlerFunction`. There is also a no-arg `http()` overload that reads the target URI from a request attribute (set by a filter, e.g. after load-balancer resolution), enabling `lb://service-id` style targets when combined with `LoadBalancerFilterFunctions.lb()`. **Config vs DSL.** You can instead declare routes as properties (`spring.cloud.gateway.mvc.routes[0]` with `id`, `uri`, `predicates`, `filters`); older/newer versions differ in prefix (`spring.cloud.gateway.mvc` vs `spring.cloud.gateway.server.webmvc`). The DSL is preferred for type safety, refactoring, and conditional logic; config is nicer for ops-managed, externally editable routing. **Multiple beans & ordering.** You may define several `RouterFunction<ServerResponse>` beans; Spring MVC combines them. The **first matching** route wins based on registration/order, so put more specific predicates before broad catch-alls, and be aware a `/**` route can shadow later ones. **Common mistakes:** - Returning `RouterFunction<ServerResponse>` but importing the **reactive** `org.springframework.web.reactive.function` types — must be the **servlet** `org.springframework.web.servlet.function` package. - Expecting `.before/.after` to be global — they are scoped to the preceding routes on that builder. - Forgetting `stripPrefix`/`rewritePath` so the upstream receives the gateway path unchanged.
- You have a /catalog/** route and a broad /** route; the broad one always wins. Why, and how do you fix it?Route matching is first-match by registration order, so a broad /** registered earlier (or in a bean ordered first) shadows the specific one. Register the specific predicate first / order that bean before the catch-all, or make the catch-all's predicate narrower.
- What's the difference between http("http://svc:8080") and the no-arg http()?http(uri) hardcodes the upstream target. The no-arg http() reads the URI from a request attribute set upstream in the filter chain — e.g. after LoadBalancerFilterFunctions.lb() resolves an lb://service-id to a concrete instance.
saying these in an interview costs you the question
- Importing reactive org.springframework.web.reactive.function types instead of servlet ones
- Thinking .before/.after are global rather than scoped to preceding routes
- Assuming route order doesn't matter