skip to content

How is the order of filters determined in Spring Cloud Gateway, and how does implementing Ordered affect a GlobalFilter?

level: middleimportance: must knowfreq 65%

answer

  1. Lower order = earlier (pre), outermost wrapper
  2. AnnotationAwareOrderComparator: Ordered + @Order + PriorityOrdered
  3. No order => 0
  4. Routing filters = LOWEST_PRECEDENCE (run last)
  5. NettyWriteResponseFilter = -1

basics

~20 s

Filters are sorted by their order value (lower runs first on the way in). A GlobalFilter can implement Ordered or use @Order to control its position. The built-in routing filters use the lowest precedence so they run last.

solid answer

~40 s

Gateway builds one combined list of the route's GatewayFilters plus every GlobalFilter (each wrapped in a GatewayFilterAdapter) and sorts it with AnnotationAwareOrderComparator — so `Ordered.getOrder()`, `@Order`, or a `PriorityOrdered` all count. Lower order value = higher precedence = runs earlier on the request ("pre") side. Because filters wrap around the `chain.filter(exchange)` call, the earliest (lowest-order) filter is outermost: its pre-logic runs first and its post-logic runs last. The built-in routing filters like `NettyRoutingFilter` sit at `Ordered.LOWEST_PRECEDENCE` (Integer.MAX_VALUE) so they execute last and actually make the downstream call. If you don't implement Ordered, your GlobalFilter defaults to order 0. Ties keep discovery/registration order.

code

java · 18 lines
java
import org.springframework.core.Ordered;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

@Component
public class TimingFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        long start = System.nanoTime();
        return chain.filter(exchange) // pre-work above, post-work below
            .then(Mono.fromRunnable(() ->
                System.out.println("took " + (System.nanoTime() - start) + "ns")));
    }
    @Override public int getOrder() { return Ordered.HIGHEST_PRECEDENCE; } // outermost
}

go deeper

for a junior

Know lower number runs earlier and routing filter is last.

for a middle

Explain Ordered/@Order, default 0, and the wrapping/unwinding behavior.

for a senior

Cite concrete built-in orders and reason about response-side placement vs NettyWriteResponseFilter.

for a principal

Design ordering across a filter suite, avoiding ties and post-commit surprises.

**Why order matters:** filters form nested wrappers around the routing call. A filter typically does pre-work, calls `chain.filter(exchange)`, and attaches post-work with `.then(...)`. The chain is a list; execution walks it front-to-back on the request path and unwinds back-to-front on the response path. So order controls both *pre* sequence and (inverted) *post* sequence. **How the order is computed:** `FilteringWebHandler` combines the route's `GatewayFilter`s with the adapted `GlobalFilter`s and sorts them via Spring's `AnnotationAwareOrderComparator`. That comparator honors: - the `org.springframework.core.Ordered` interface (`int getOrder()`), - the `@Order(n)` annotation, - `PriorityOrdered` (sorted ahead of plain Ordered). **Semantics of the number:** **lower value = higher priority = earlier on the pre side.** `Ordered.HIGHEST_PRECEDENCE = Integer.MIN_VALUE`, `Ordered.LOWEST_PRECEDENCE = Integer.MAX_VALUE`. A filter with no order info is treated as order `0`. **Making a GlobalFilter ordered** — two equivalent ways: ```java @Component class AuthFilter implements GlobalFilter, Ordered { public Mono<Void> filter(ServerWebExchange e, GatewayFilterChain c) { return c.filter(e); } public int getOrder() { return -100; } // runs early } ``` or `@Component @Order(-100) class AuthFilter implements GlobalFilter { ... }`. **Built-in ordering you should know:** - `NettyWriteResponseFilter` — order `-1` (writes the proxied response back; needs to run its *post* logic after the routing filter produced the response). - `ForwardRoutingFilter` and `NettyRoutingFilter` — `Ordered.LOWEST_PRECEDENCE` (Integer.MAX_VALUE); they are terminal and run last. - `RouteToRequestUrlFilter` — order `10000`; computes the target URL before the routing filters. - `ReactiveLoadBalancerClientFilter` — order `10150`; resolves `lb://` to a concrete host before Netty routing. **Gotchas:** - If your GlobalFilter must see/modify the *response* (e.g. add a header), remember post-logic runs in reverse order, and the response body may already be committed by `NettyWriteResponseFilter` — placement matters. - Don't put a filter at LOWEST_PRECEDENCE expecting it to run *after* routing on the pre side and still continue — the routing filters are terminal and don't call `chain.filter`, so anything ordered after them on the pre side never executes its pre-logic before the call. - Two filters with the same order have undefined relative order beyond registration order; set explicit orders when it matters.

  • If filter A has order -1 and filter B has order 1, which one's post-processing runs first?
    B's post runs first. A is outermost (runs pre first, post last); B is inner. On the unwind, the innermost post executes first, so B's `.then(...)` completes before A's.
  • What order does a GlobalFilter get if it neither implements Ordered nor has @Order?
    0. AnnotationAwareOrderComparator treats an unordered element as order 0, placing it between negative-ordered (earlier) and positive-ordered (later) filters.

saying these in an interview costs you the question

  • Saying higher order value runs first
  • Believing post-processing runs in the same order as pre-processing
  • Assuming a filter ordered after NettyRoutingFilter will still run its pre-logic before the proxied call

context