Spring Cloud Gateway: Custom Filters and Filter Factories

Custom Gateway Filter Implementation

When you need to implement custom logic within the gateway's request processing pipeline, the GatewayFilter interface provides the necessary hooks. Below is a timing filter that meausres request duration:

import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.core.Ordered;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

public class RequestTimerFilter implements GatewayFilter, Ordered {

    private static final String TIMER_START_ATTR = "requestStartTime";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        exchange.getAttributes().put(TIMER_START_ATTR, System.currentTimeMillis());
        
        return chain.filter(exchange).then(
            Mono.fromRunnable(() -> {
                Long startMillis = exchange.getAttribute(TIMER_START_ATTR);
                if (startMillis != null) {
                    long duration = System.currentTimeMillis() - startMillis;
                    System.out.println(
                        exchange.getRequest().getURI().getRawPath() + 
                        ": " + 
                        duration + "ms"
                    );
                }
            })
        );
    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }
}

The filter captures the current timestamp before chain.filter(exchange) executes and calculates elapsed time in the then() block. Code executed before chain.filter(exchange) represents the "pre" phase, while code within then() represents the "post" phase.

To register this filter with a route:

@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
    return builder.routes()
        .route(r -> r.path("/api/users/**")
            .filters(f -> f.stripPrefix(1)
                .filter(new RequestTimerFilter())
                .addResponseHeader("X-Response-Default-Foo", "Default-Bar"))
            .uri("lb://USER-SERVICE")
            .order(0)
            .id("user_service_route")
        )
        .build();
}

Accessing http://localhost:20000/api/users/hello/cloud produces output like:

2020-08-23 17:17:43.584 INFO 9856 — [ctor-http-nio-1] o.s.cloud.gateway.filter.GatewayFilter : /hello/cloud: 1ms

Global Filters

Route-specific filters require configuration for each route definition. For cross-cutting concerns like authentication that should apply to all routes, global filters offer a cleaner solution.

Implementing a global filter is nearly identical to a custom filter—just implement GlobalFilter instead of GatewayFilter:

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

public class AuthenticationFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String authToken = exchange.getRequest().getHeaders().getFirst("Authorization");
        if (authToken == null || authToken.isEmpty()) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return -100;
    }
}

Register the filter as a Spring bean:

@Bean
public AuthenticationFilter authenticationFilter() {
    return new AuthenticationFilter();
}

The filter logs when processing requests:

2020-08-23 17:54:10.574 DEBUG 20452 — [ctor-http-nio-4] o.s.c.g.h.RoutePredicateHandlerMapping : Route matched: service_customer
2020-08-23 17:54:10.579 DEBUG 20452 — [ctor-http-nio-4] o.s.c.g.h.RoutePredicateHandlerMapping : Mapping [Exchange: GET http://localhost:20000/customer/hello/cloud] to Route{id='service_customer', uri=lb://EUREKA-CONSUMER, order=0, predicate=Paths: [/customer/**], match trailing slash: true, gatewayFilters=[[[StripPrefix parts = 1], order = 1], [[AddResponseHeader X-Response-Default-Foo = 'Default-Bar'], order = 2]], metadata={}}
2020-08-23 17:54:10.579 DEBUG 20452 — [ctor-http-nio-4] o.s.c.g.h.RoutePredicateHandlerMapping : [b6c683f3-7] Mapped to org.springframework.cloud.gateway.handler.FilteringWebHandler@28fcb031
2020-08-23 17:54:10.579 DEBUG 20452 — [ctor-http-nio-4] o.s.c.g.handler.FilteringWebHandler : Sorted gatewayFilterFactories: [[GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.RemoveCachedBodyFilter@15ccd9e2}, order = -2147483648], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.AdaptCachedBodyGlobalFilter@3dc6b2a1}, order = -2147482648], [GatewayFilterAdapter{delegate=com.example.filter.AuthenticationFilter@633d9a32}, order = -100], ...]

Custom Filter Factories

Spring Cloud Gateway provides built-in filter factories like StripPrefix and AddResponseHeader that can be configured declaratively:

filters:
  - StripPrefix=1
  - AddResponseHeader=X-Response-Default-Foo, Default-Bar

Creating a custom filter factory allows parameterized configuration. The following example extends AbstractGatewayFilterFactory to create a configurable timing filter:

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.List;

public class TimingGatewayFilterFactory 
    extends AbstractGatewayFilterFactory<TimingGatewayFilterFactory.Config> {

    private static final Log log = LogFactory.getLog(GatewayFilter.class);
    private static final String REQUEST_START_ATTR = "requestStartTime";
    private static final String INCLUDE_QUERY_PARAM = "includeQuery";

    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList(INCLUDE_QUERY_PARAM);
    }

    public TimingGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            exchange.getAttributes().put(REQUEST_START_ATTR, System.currentTimeMillis());
            return chain.filter(exchange).then(
                Mono.fromRunnable(() -> {
                    Long startTime = exchange.getAttribute(REQUEST_START_ATTR);
                    if (startTime != null) {
                        StringBuilder output = new StringBuilder()
                            .append(exchange.getRequest().getURI().getRawPath())
                            .append(": ")
                            .append(System.currentTimeMillis() - startTime)
                            .append("ms");
                        if (config.isIncludeQuery()) {
                            output.append(" params:").append(exchange.getRequest().getQueryParams());
                        }
                        log.info(output.toString());
                    }
                })
            );
        };
    }

    public static class Config {
        private boolean includeQuery;

        public boolean isIncludeQuery() {
            return includeQuery;
        }

        public void setIncludeQuery(boolean includeQuery) {
            this.includeQuery = includeQuery;
        }
    }
}

Two abstract base classes simplify filter factory development:

Base Class Parameter Count Example
AbstractGatewayFilterFactory Single parameter StripPrefix, RequestSize
AbstractNameValueGatewayFilterFactory Two parameters AddRequestHeader, AddResponseHeader

The Config inner class receives the filter's configuration parameters. The shortcutFieldOrder() method maps configuration values to class fields. The parent constructor must be called with the Config class to avoid ClassCastException.

Register the factory as a Spring bean:

@Bean
public TimingGatewayFilterFactory timingGatewayFilterFactory() {
    return new TimingGatewayFilterFactory();
}

Configuration in YAML:

spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
      default-filters:
        - Timing=true
      routes:
        - id: customer_service
          uri: lb://CONSUMER
          order: 0
          predicates:
            - Path=/customer/**
          filters:
            - StripPrefix=1
            - AddResponseHeader=X-Response-Default-Foo, Default-Bar

Requesting http://localhost:20000/customer/hello/user?token=1000 produces:

2018-05-08 16:53:02.030 INFO 84423 — [ctor-http-nio-1] o.s.cloud.gateway.filter.GatewayFilter : /hello/user: 656ms params:{token=[1000]}

Tags: Spring Cloud Gateway gateway filter Global Filter Filter Factory microservices

Posted on Tue, 19 May 2026 09:32:19 +0000 by erikwebb