Skip to content

Filters

Filters are middleware that run before matching routes. Register them with wildcard paths.

Use wildcard paths to match multiple routes:

// Applies to all routes under /api/
routesRegister.jsonFilter("/api/*", appState, new JwtFilter(jwtProvider));

Filters run in registration order before the matching route handler. If any filter completes with an error result, the route handler is not invoked.

A filter implements JsonFilter<APP> and receives an HttpStream<Void, APP>:

public class VisitedFilter implements JsonFilter<MyState> {
@Override
public RequestPipeline<Void> handle(HttpStream<Void, MyState> e) {
return e.complete(ctx -> {
ctx.session().addResponseCookie(new HttpCookie("visited", "true"));
return HttpResult.success();
});
}
}

A filter’s stream is the same HttpStream type handlers receive, so the entire pipeline API is available: validate(), requireJwt(), map(), flatMap(), blockingMap(), blockingFlatMap(), asyncMap(), peek(), and so on. You can short-circuit a request (e.g. return 401 from flatMap) or enrich the downstream context before the route handler runs.

The one thing filters cannot do is read the JSON request body. The stream’s input type is Void, because a single filter can be registered against a wildcard path that matches many routes with different (or no) body types — there is no single type the framework can deserialize the body into. Calling ctx.in() inside a filter returns null.

Everything else on the request is available through ctx.session() and ctx.app():

  • Request headers — ctx.session().getRequestHeader("...")
  • Query parameters — ctx.session().getQueryParam("...") (and via v.queryParam(...) in validate())
  • Path parameters — ctx.session().getPathParam("...") (and via v.pathParam(...) in validate())
  • Cookies — read via ctx.session(), write via addResponseCookie(...)
  • Response headers — ctx.session().addResponseHeader(...)
  • Application state — ctx.app()
  • JWT — requireJwt(jwtProvider) works exactly as it does in a handler
public class JwtFilter implements JsonFilter<MyState> {
private final JwtProvider jwtProvider;
public JwtFilter(JwtProvider jwtProvider) {
this.jwtProvider = jwtProvider;
}
@Override
public RequestPipeline<Void> handle(HttpStream<Void, MyState> e) {
return e.requireJwt(jwtProvider).complete();
}
}

Because validate() lives on HttpStream, filters can also validate query and path parameters before requests reach the route — but they cannot validate body fields (there is no body object to validate).

return e
.validate(v -> v.queryParam("tenant").required().matches("[a-z0-9-]+"))
.complete();