skip to content

How would you implement a custom GatewayFilter factory, and what are the key extension points (config binding, shortcut args, ordering)?

level: principalimportance: should knowfreq 35%

answer

  1. extends AbstractGatewayFilterFactory<Config>
  2. super(Config.class) for binding
  3. Class name must end GatewayFilterFactory
  4. shortcutFieldOrder() enables compact syntax
  5. OrderedGatewayFilter / Ordered for precedence

basics

~10 s

Extend AbstractGatewayFilterFactory<Config> with a nested Config POJO, register it as a @Component whose class name ends in GatewayFilterFactory, implement apply(config) to return the GatewayFilter, and override shortcutFieldOrder() to enable the compact YAML syntax.

solid answer

~40 s

You extend `AbstractGatewayFilterFactory<Config>`, passing `Config.class` to the super constructor so Spring can bind route args to a config POJO. The class must be a Spring bean and its name must end in `GatewayFilterFactory` — the short name used in YAML is the prefix (e.g. `MyThingGatewayFilterFactory` → `MyThing`). You implement `apply(Config config)` to return a `GatewayFilter` lambda `(exchange, chain) -> ...` capturing the config; there you do pre-phase request mutation via `exchange.mutate()` and/or post-phase work via `chain.filter(exchange).then(...)`. Override `shortcutFieldOrder()` to list config field names in positional order so `MyThing=a, b` binds without the verbose args form. For ordering relative to other filters, wrap the returned filter in `OrderedGatewayFilter` or have it implement `Ordered`. Optionally override `name()` to change the short name. This is how all built-in factories (AddRequestHeader, etc.) are implemented.

code

java · 35 lines
java
@Component
public class AddHeaderIfMissingGatewayFilterFactory
        extends AbstractGatewayFilterFactory<AddHeaderIfMissingGatewayFilterFactory.Config> {

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

    @Override
    public List<String> shortcutFieldOrder() {
        return List.of("name", "value"); // enables: AddHeaderIfMissing=X-Foo, bar
    }

    @Override
    public GatewayFilter apply(Config config) {
        return new OrderedGatewayFilter((exchange, chain) -> {
            if (exchange.getRequest().getHeaders().containsKey(config.getName())) {
                return chain.filter(exchange); // pre phase: skip if present
            }
            ServerHttpRequest mutated = exchange.getRequest().mutate()
                    .header(config.getName(), config.getValue())
                    .build();
            return chain.filter(exchange.mutate().request(mutated).build());
        }, 0);
    }

    public static class Config {
        private String name;
        private String value;
        public String getName() { return name; }
        public void setName(String name) { this.name = name; }
        public String getValue() { return value; }
        public void setValue(String value) { this.value = value; }
    }
}

go deeper

for a junior

Aware you can write custom filters but not the mechanics.

for a middle

Can extend AbstractGatewayFilterFactory and implement apply with a config POJO.

for a senior

Knows naming convention, shortcutFieldOrder, and pre/post mutation within apply.

for a principal

Discusses ordering strategy, validation, reactive non-blocking discipline, and GatewayFilter-vs-GlobalFilter boundary for reusable edge policy.

Custom GatewayFilter factories let you package reusable per-route logic that reads like the built-ins in configuration. **Base class.** Extend `AbstractGatewayFilterFactory<C>` where `C` is your **config** type. The base class provides argument binding, name derivation, and shortcut parsing. Pass the config class to the super constructor: ```java public MyThingGatewayFilterFactory() { super(Config.class); } ``` Without that, Spring cannot instantiate/bind your config. **Naming convention.** The bean's simple class name **must end in `GatewayFilterFactory`**. The **short name** referenced in YAML is everything before that suffix: `AddRequestHeaderGatewayFilterFactory` → `AddRequestHeader`. To use a different short name, override `name()`. **Registration.** Annotate with `@Component` (or declare a `@Bean`) so it's in the context. Spring Cloud Gateway discovers all `GatewayFilterFactory` beans at startup and maps them by short name. **Config binding.** Define a nested config POJO with getters/setters. In YAML the expanded form binds by field name: ```yaml filters: - name: MyThing args: headerName: X-Foo headerValue: bar ``` **Shortcut args.** Override `shortcutFieldOrder()` returning the field names in positional order to allow the compact form `MyThing=X-Foo, bar`. If you don't, only the expanded `args` map works. There are also hooks like `shortcutType()` for less common parsing needs. **apply().** The core method: ```java @Override public GatewayFilter apply(Config config) { return (exchange, chain) -> { // pre phase: mutate request ServerHttpRequest req = exchange.getRequest().mutate() .header(config.getHeaderName(), config.getHeaderValue()).build(); return chain.filter(exchange.mutate().request(req).build()); // or post phase via chain.filter(exchange).then(...) }; } ``` The returned `GatewayFilter` closes over `config`, so one factory instance produces per-route configured filters. **Ordering.** By default filters run in the order listed on the route, interleaved with built-in filters at their fixed orders (routing/write-response filters occupy known positions). To control precedence, wrap your filter: ```java return new OrderedGatewayFilter((exchange, chain) -> {...}, order); ``` or return a filter implementing `Ordered`. Lower order runs earlier in the pre phase (and correspondingly its post block unwinds later). **Validation.** You can annotate config fields with Bean Validation (`@NotEmpty`, etc.); enabling validation causes bad route config to fail fast at startup. **Reactive discipline in apply().** Because this runs on WebFlux, never block. Don't call blocking I/O inside the filter; compose reactively (`flatMap`, `then`). Remember request/response are single-consumption for bodies — for body access use the provided body-modification helpers or decorators rather than reading the `Flux<DataBuffer>` yourself. **GatewayFilter vs GlobalFilter.** A factory produces per-route `GatewayFilter`s bound in config. If you want logic on *every* route without configuring it per route, you implement `GlobalFilter` instead (separate concern). AbstractGatewayFilterFactory is specifically the per-route extension point. **When to build one.** Prefer built-ins; write a custom factory when you need parameterized, reusable edge logic that teams attach declaratively to selected routes — e.g. a conditional header, a tenant-resolution step, custom auth pre-checks — while keeping the compact config ergonomics.

  • What determines the short name used in YAML for a custom factory?
    The bean's simple class name with the GatewayFilterFactory suffix stripped (MyThingGatewayFilterFactory -> MyThing), unless you override name().
  • What does shortcutFieldOrder() enable, and what happens without it?
    It maps positional shortcut args to config fields so you can write MyThing=a, b. Without it, only the verbose 'name/args' map form binds.
  • How do you control where your custom filter runs relative to built-in filters?
    Return an OrderedGatewayFilter (or a filter implementing Ordered) with an explicit order value; lower order runs earlier in the pre phase.

saying these in an interview costs you the question

  • Forgetting super(Config.class), so config binding fails
  • Naming the class without the GatewayFilterFactory suffix and expecting discovery
  • Blocking I/O inside apply()'s filter lambda on the reactive stack
  • Confusing a per-route GatewayFilter factory with a GlobalFilter
  • Thinking apply() runs per request rather than per route at config time

context