Mastering Spring MVC Functional Endpoints: Error Handling, Filters, and Interceptors
This article extends the previous introduction to Spring MVC functional interfaces by demonstrating how to handle exceptions, apply filters, implement before/after interceptors, and achieve unified routing and error handling using functional programming style in Java.
1. Introduction
The previous article introduced functional interface programming in Spring MVC, showing that despite its unfamiliar syntax, it offers powerful capabilities. This piece continues the discussion, focusing on functional endpoints and addressing new challenges developers may encounter.
2. New Issues
When adopting the functional style, several practical problems arise. The following sections illustrate solutions with concrete examples.
2.1 How to Handle Exceptions
Exception handling can be performed directly in the functional route definition:
/**
* 接口附带异常处理逻辑.
*
* @param userService the user service
* @return the user by name with error handle
*/
public RouterFunction<ServerResponse> withErrorHandle() {
return RouterFunctions.route()
.GET("/userwitherrorhandle/{username}",
request -> ServerResponse.ok()
.body(userService.getByName(request.pathVariable("username"))))
// 异常处理
.onError(RuntimeException.class,
(e, request) -> EntityResponse.fromObject(e.getMessage())
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.build())
.build();
}Use the onError method (or its overloads) to provide custom error responses; unified exception handling will be covered later.
2.2 How to Use Filters
Many existing Spring MVC projects rely on filters. The functional API offers HandlerFilterFunction for similar purposes:
/**
* 对特定接口指定过滤器.
*
* @param userService the user service
* @return the router function
*/
public RouterFunction<ServerResponse> withFilter() {
return RouterFunctions.route().POST("/save",
request -> ServerResponse.ok()
.body(userService.saveUser(request.body(UserInfo.class))))
// 执行了一个过滤器逻辑 参数携带了 save 放行 否则返回 bad request 并附带消息
.filter((request, next) -> request.param("save").isPresent() ?
next.handle(request) :
ServerResponse.status(HttpStatus.BAD_REQUEST).body("no save"))
.build();
}The filter method enables logging, security policies, CORS handling, and other cross‑cutting concerns.
2.3 How to Use Interceptors
The functional style does not expose Spring MVC's traditional interceptor API, but it provides before/after handlers that achieve equivalent behavior:
public RouterFunction<ServerResponse> getUserByName() {
return RouterFunctions.route()
.GET("/user/{username}",
request -> ServerResponse.ok()
.body(userService.getByName(request.pathVariable("username"))))
// 前置处理 打印 path
.before(serverRequest -> {
log.info(serverRequest.path());
return serverRequest;
})
// 后置处理 如果响应状态为200 则打印 response ok
.after((serverRequest, serverResponse) -> {
if (serverResponse.statusCode() == HttpStatus.OK) {
log.info("response ok");
}
return serverResponse;
}).build();
}When a request such as /user/{username} arrives, the before and after functions execute respectively.
2.4 How to Achieve Unified Handling
In traditional development each controller handles a specific domain, often prefixed with /v1/user or /v1/order. Functional routing can group endpoints and apply common filters, interceptors, and error handling.
@Bean
RouterFunction<ServerResponse> userEndpoints(UserController userController) {
return RouterFunctions.route()
.path("/v2/user", builder -> builder
// /get/{username} -> /v2/user//get/{username}
.add(userController.getUserByName()
// /del/{username} -> /v2/user//del/{username}
.and(userController.delUser()
// /save -> /v2/user/save
.and(userController.saveUser()
// /update -> /v2/user/update
.and(userController.updateUser())))))
.build();
}You can also use RouterFunctions.route().nest to group routes and apply a unified onError handler:
@Bean
RouterFunction<ServerResponse> nestEndpoints(UserController userController) {
return RouterFunctions.route().nest(RequestPredicates.path("/v1/user"),
builder -> builder
.add(userController.getUserByName())
.add(userController.delUser())
.add(userController.saveUser())
.add(userController.updateUser()))
// 对上述路由进行统一的异常处理
.onError(RuntimeException.class,
(throwable, serverRequest) -> ServerResponse
.status(HttpStatus.BAD_REQUEST)
.body("bad req"))
.build();
}3. Conclusion
This article briefly explains how Spring MVC functional development mirrors traditional features such as filters, interceptors, and route aggregation, offering greater flexibility. While the functional style may feel unfamiliar to developers accustomed to imperative code, it is increasingly adopted and worth mastering for long‑term backend development.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
