You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Controller Advice无法正确处理异常完成的CompletableFuture

解决Spring Boot 1.5异步控制器CompletableFuture异常无法被ControllerAdvice捕获的问题

这个问题我之前在项目里也碰到过,Spring Boot 1.5对异步返回CompletableFuture的异常处理确实有特殊的地方,默认的@ControllerAdvice抓不到你抛出的UserNotFoundException,下面给你梳理原因和解决方案:

为什么默认的ControllerAdvice不生效?

当你的UserService.findById()抛出UserNotFoundException时,这个异常会被CompletableFuture包裹为CompletionException,然后Spring MVC在处理异步返回值时,会把这个包装后的异常传递给异常处理机制。而你原来的@ExceptionHandler只监听UserNotFoundException,自然不会触发。

解决方案:捕获CompletionException并提取原始异常

你只需要在全局异常处理器中添加对CompletionException的处理,从里面提取出真正的UserNotFoundException即可。

完整的全局异常处理器示例

@ControllerAdvice
public class GlobalExceptionHandler {

    // 处理CompletableFuture包装的异常
    @ExceptionHandler(CompletionException.class)
    public ResponseEntity<ErrorResponse> handleCompletionException(CompletionException ex) {
        // 提取原始异常
        Throwable cause = ex.getCause();
        if (cause instanceof UserNotFoundException) {
            UserNotFoundException userEx = (UserNotFoundException) cause;
            return ResponseEntity.status(HttpStatus.NOT_FOUND)
                    .body(new ErrorResponse("用户不存在", userEx.getMessage()));
        }
        // 处理其他类型的异常
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(new ErrorResponse("服务器内部错误", ex.getMessage()));
    }

    // 保留原有的同步异常处理(可选)
    @ExceptionHandler(UserNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleUserNotFoundException(UserNotFoundException ex) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND)
                .body(new ErrorResponse("用户不存在", ex.getMessage()));
    }

    // 自定义错误响应体(根据你的需求调整)
    public static class ErrorResponse {
        private String message;
        private String detail;

        public ErrorResponse(String message, String detail) {
            this.message = message;
            this.detail = detail;
        }

        // getter和setter省略
    }
}

额外说明

  • 这个方案是全局生效的,不需要在每个异步控制器方法里单独处理异常,适合项目中多个异步接口的场景。
  • 如果你的业务需要,也可以在CompletableFuture的调用链中用exceptionally()方法提前处理异常,比如:
@GetMapping("/{id}/address")
public CompletableFuture<Address> getAddress(@PathVariable String id) {
    return service.findById(id)
            .thenApply(User::getAddress)
            .exceptionally(ex -> {
                if (ex.getCause() instanceof UserNotFoundException) {
                    // 这里可以直接抛出原始异常,或者包装后抛出,最终都会被全局处理器捕获
                    throw new CompletionException(ex.getCause());
                }
                throw new CompletionException(ex);
            });
}

内容的提问来源于stack exchange,提问作者riccardo.cardin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 08:08:55