Different ways to validate requests: Spring WebFlux RouterFunctions

Sam Ko
3 min readApr 16, 2020

--

Photo by Kouji Tsuru on Unsplash

When defining your Spring Boot 2.0 APIs with RouterFunctions, there are a few patterns you can use to filter and validate requests, as well as return default responses for invalid requests.

Scenario

Let us say, for example, we want to define an API endpoint that accepts authId as a path variable. We’re not using the authId for anything, but we’ll capture it as a path variable using {}

@Configuration
public class Routers {
@Bean
public RouterFunction<ServerResponse> myRoute() {
return route(GET("/authId/{authId}"), this::respondOk);
}

private Mono<ServerResponse> respondOk(ServerRequest request) {
return ServerResponse.ok().build();
}
}

Goal: we want to validate the authId to make sure that it only has numbers and return a BAD_REQUEST when we encounter an invalid authId.

RouterFunction Composition and Pattern Matching

With the help of function composition, we can compose multiple routes together that can respond appropriately based on Spring Path Patterns.

We can define two routes:

  • /authId/{authId:[0-9]+}that accepts numbers from 0–9, and returns OK
  • /authId/{authId} that accepts any value (including non-number characters), and returns BAD_REQUEST

We can combine both of these routes together using RouterFunction#andRoute to produce a new, composed route that will direct the request to the appropriate handler.

@Configuration
public class Routers {
@Bean
public RouterFunction<ServerResponse> myRoute() {
return route(GET("/authId/{authId:[0-9]+}"), this::respondOk)
.andRoute(GET("/authId/{authId}"), this::badRequest);
}

private Mono<ServerResponse> badRequest(ServerRequest request) {
return ServerResponse.badRequest().build();
}

private Mono<ServerResponse> respondOk(ServerRequest request) {
return ServerResponse.ok().build();
}
}

Let’s say we get a request 'GET /authId/123456789' . Our request will be evaluated against all defined router functions in order, and return the first handler function that matches. In this case, since our request matches the criteria for the path pattern {authId:[0-9]+} , this::respondOk handler will be returned.

In another request 'GET /authId/12345abcd' , our request does not fulfill the requirements of the first route, so the request will be matched against the second route. In this case, this::badRequest will be returned as the handler to handle the request.

If none of the requests match, say 'GET /authId/1234/abcd' , a default handler will be used that returns NOT_FOUND . This default handler is automatically provided to you when you start up your Spring Boot application.

This method of request filtering and validating may be ideal and easier-to-read for users that are familiar with exhaustive pattern matching, most commonly found in Functional Programming languages.

RouterFunction Filters

You can define filters that act as middleware between your request and your handler method via RouterFunction#filter .

route(GET("/authId/{authId}"), this::respondOk)
.filter((request, next) -> {
try {
Long.parseLong(request.pathVariable("authId"));

//pass to next handler in chain
return next.handle(request);
} catch (NumberFormatException e) {

//couldn't parse authId
return ServerResponse.badRequest().build();
}
});

The route will accept any value in {authId} , but our filter will validate the authId to ensure that it contains only numbers. If the authId is valid, it will pass the request down the chain to the next handler function. Otherwise, it returns a BAD_REQUEST ServerResponse.

Note: the last filter function that calls next.handle(request) will execute your original handler function (in the code above, it is this::respondOk ).

You can define any number of filters and you can re-use filters across different routes.

Final Thoughts

This will, of course, extend into more complex use cases and other request parameters such as queryParams and headers. Use the best pattern that works for your use case!

--

--

Sam Ko

Passionate about Spring Boot, GraphQL, and Reactive Programming