Skip to content

RouterFunction

Functional routing provides a concise, fluent alternative to @RestController. Introduced in the Spring 5 era, RouterFunction remains fully supported in Spring Framework 7. RouterFunction lets you compose routes and handlers using a functional API instead of controller classes.

For example, there is a simple controller class.

@RestController
@RequestMapping
class MessageController {

    @GetMapping
    Flux<Message> allMessages(){
        return Flux.just(
            Message.builder().body("hello Spring Framework 7").build(),
            Message.builder().body("hello Spring Boot 4").build()
        );
    }

}

You can write the same functionality with a RouterFunction bean instead.

@Bean
public RouterFunction<ServerResponse> routes() {
    return route(GET("/"),(ServerRequest req)-> ok()
                 .body(
                     BodyInserters.fromValue(
                         Arrays.asList(
                             Message.builder().body("hello Spring Framework 7").build(),
                             Message.builder().body("hello Spring Boot 4").build()
                         )
                     )
                 )
                );
}

The route (from RouterFunctions) accepts a RequestPredicate and a HandlerFunction. HandlerFunction is a @FunctionalInterface.

RouterFunctions and RequestPredicates are helpers to make it easy to assemble the request routes and predicates.

@FunctionalInterface
public interface HandlerFunction<T extends ServerResponse> {
    Mono<T> handle(ServerRequest var1);
}

For the complete codes, check spring-reactive-sample/boot-start and spring-reactive-sample/boot-start-routes.

You can extract the handler codes into a new class.

The following code fragments demonstrates the same features of PostController but written in RouterFunction.

@Bean
public RouterFunction<ServerResponse> routes(PostHandler postController) {
    return route(GET("/posts"), postController::all)
        .andRoute(POST("/posts"), postController::create)
        .andRoute(GET("/posts/{id}"), postController::get)
        .andRoute(PUT("/posts/{id}"), postController::update)
        .andRoute(DELETE("/posts/{id}"), postController::delete);
}

The implementation of HandlerFunction are centralized in a PostHandler class.

@Component
class PostHandler {

    private final PostRepository posts;

    public PostHandler(PostRepository posts) {
        this.posts = posts;
    }

    public Mono<ServerResponse> all(ServerRequest req) {
        return ServerResponse.ok().body(this.posts.findAll(), Post.class);
    }

    public Mono<ServerResponse> create(ServerRequest req) {
        return req.bodyToMono(Post.class)
            .flatMap(post -> this.posts.save(post))
            .flatMap(p -> ServerResponse.created(URI.create("/posts/" + p.getId())).build());
    }

    public Mono<ServerResponse> get(ServerRequest req) {
        return this.posts.findById(req.pathVariable("id"))
            .flatMap(post -> ServerResponse.ok().body(Mono.just(post), Post.class))
            .switchIfEmpty(ServerResponse.notFound().build());
    }

    public Mono<ServerResponse> update(ServerRequest req) {

        return Mono
            .zip(
                (data) -> {
                    Post p = (Post) data[0];
                    Post p2 = (Post) data[1];
                    p.setTitle(p2.getTitle());
                    p.setContent(p2.getContent());
                    return p;
                },
                this.posts.findById(req.pathVariable("id")),
                req.bodyToMono(Post.class)
            )
            .cast(Post.class)
            .flatMap(post -> this.posts.save(post))
            .flatMap(post -> ServerResponse.noContent().build());

    }

    public Mono<ServerResponse> delete(ServerRequest req) {
        return ServerResponse.noContent().build(this.posts.deleteById(req.pathVariable("id")));
    }

}

For the complete codes, check spring-reactive-sample/boot-routes.

NOTE: The RouterFunction is ported back to Servlet stack since 5.2, check my example hantsy/spring-webmvc-functional-sample.