How would you implement a custom GatewayFilter factory, and what are the key extension points (config binding, shortcut args, ordering)?
answer
- extends AbstractGatewayFilterFactory<Config>
- super(Config.class) for binding
- Class name must end GatewayFilterFactory
- shortcutFieldOrder() enables compact syntax
- OrderedGatewayFilter / Ordered for precedence
basics
~10 sExtend 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 sYou 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@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
Aware you can write custom filters but not the mechanics.
Can extend AbstractGatewayFilterFactory and implement apply with a config POJO.
Knows naming convention, shortcutFieldOrder, and pre/post mutation within apply.
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