Elegant Unified Response and Global Exception Handling in Spring Boot

The article explains how Spring Boot projects can adopt a standard JSON response structure using a generic ResponseVO class and achieve automatic wrapping and global exception handling through a custom @ResponseResultBody annotation combined with ResponseBodyAdvice and @ControllerAdvice, reducing repetitive code and improving robustness.

Shepherd Advanced Notes
Shepherd Advanced Notes
Shepherd Advanced Notes
Elegant Unified Response and Global Exception Handling in Spring Boot

Overview

Most modern Spring Boot applications expose RESTful JSON APIs, so defining a single response format simplifies front‑end integration and UI rendering.

Unified Response Structure

A generic ResponseVO<T> class is introduced with three fields: code (status code), msg (description), and data (payload). Static factory methods success and failure create instances for successful or erroneous outcomes.

@Data
public class ResponseVO<T> implements Serializable {
    private Integer code;
    private String msg;
    private T data;
    public ResponseVO() {}
    public ResponseVO(Integer code, String msg) { this.code = code; this.msg = msg; }
    public ResponseVO(Integer code, T data) { this.code = code; this.data = data; }
    public ResponseVO(Integer code, String msg, T data) { this.code = code; this.msg = msg; this.data = data; }
    private ResponseVO(ResponseStatusEnum resultStatus, T data) {
        this.code = resultStatus.getCode();
        this.msg = resultStatus.getMsg();
        this.data = data;
    }
    public static ResponseVO<Void> success() { return new ResponseVO<Void>(ResponseStatusEnum.SUCCESS, null); }
    public static <T> ResponseVO<T> success(T data) { return new ResponseVO<T>(ResponseStatusEnum.SUCCESS, data); }
    public static <T> ResponseVO<T> success(ResponseStatusEnum resultStatus, T data) {
        if (resultStatus == null) { return success(data); }
        return new ResponseVO<T>(resultStatus, data);
    }
    public static <T> ResponseVO<T> failure() { return new ResponseVO<T>(ResponseStatusEnum.SYSTEM_ERROR, null); }
    public static <T> ResponseVO<T> failure(ResponseStatusEnum resultStatus) { return failure(resultStatus, null); }
    public static <T> ResponseVO<T> failure(ResponseStatusEnum resultStatus, T data) {
        if (resultStatus == null) { return new ResponseVO<T>(ResponseStatusEnum.SYSTEM_ERROR, null); }
        return new ResponseVO<T>(resultStatus, data);
    }
    public static <T> ResponseVO<T> failure(Integer code, String msg) { return new ResponseVO<T>(code, msg); }
}

Example JSON for a successful call:

{
  "code": 200,
  "msg": "OK",
  "data": {
    "id": 123,
    "name": "shepherd"
  }
}

Example JSON for a business error:

{
  "code": 400,
  "msg": "当前用户不存在"
}

Using the Wrapper in Controllers

A controller method returns ResponseVO<User> and calls ResponseVO.success(user):

@GetMapping("/test/user")
public ResponseVO<User> testUser() {
    User user = new User();
    user.setId(123L);
    user.setName("shepherd");
    return ResponseVO.success(user);
}

Manually wrapping each endpoint becomes repetitive, especially in large projects.

Advanced Automatic Wrapping with ResponseBodyAdvice

Spring Boot’s ResponseBodyAdvice can intercept controller return values. A custom implementation ResponseResultBodyAdvice is created and annotated with @RestControllerAdvice:

@RestControllerAdvice
@Slf4j
public class ResponseResultBodyAdvice implements ResponseBodyAdvice<Object> {
    @Resource
    private ObjectMapper objectMapper;
    private static final Class<? extends Annotation> ANNOTATION_TYPE = ResponseResultBody.class;
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ANNOTATION_TYPE)
                || returnType.hasMethodAnnotation(ANNOTATION_TYPE);
    }
    @SneakyThrows
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
                                  Class<? extends HttpMessageConverter<?>> selectedConverterType,
                                  ServerHttpRequest request, ServerHttpResponse response) {
        Class<?> returnClass = returnType.getMethod().getReturnType();
        if (body instanceof String || Objects.equals(returnClass, String.class)) {
            String value = objectMapper.writeValueAsString(ResponseVO.success(body));
            return value;
        }
        if (body instanceof ResponseVO) {
            return body;
        }
        return ResponseVO.success(body);
    }
}

A custom marker annotation is defined:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@ResponseBody
public @interface ResponseResultBody {}

The advice is registered as a bean:

@Bean
public ResponseResultBodyAdvice responseResultBodyAdvice() {
    return new ResponseResultBodyAdvice();
}

When a controller class or method is annotated with @ResponseResultBody, the advice automatically wraps the return value in ResponseVO, handling the special case of String responses and preventing double‑wrapping.

Global Exception Handling

To capture unchecked runtime exceptions, a @ControllerAdvice class with @ExceptionHandler methods returns a unified ResponseVO error payload:

@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(Exception.class)
    public ResponseVO exceptionHandler(Exception e) {
        if (e instanceof BizException) {
            BizException be = (BizException) e;
            if (be.getCode() == null) {
                be.setCode(ResponseStatusEnum.BAD_REQUEST.getCode());
            }
            return ResponseVO.failure(be.getCode(), be.getMessage());
        } else if (e instanceof MethodArgumentNotValidException) {
            MethodArgumentNotValidException ex = (MethodArgumentNotValidException) e;
            Map<String, String> map = new HashMap<>();
            ex.getBindingResult().getFieldErrors().forEach(item -> map.put(item.getField(), item.getDefaultMessage()));
            log.error("Data validation error:", e);
            return ResponseVO.failure(ResponseStatusEnum.BAD_REQUEST, map);
        } else if (e instanceof HttpRequestMethodNotSupportedException) {
            log.error("Request method error:", e);
            return ResponseVO.failure(ResponseStatusEnum.BAD_REQUEST.getCode(), "请求方法不正确");
        } else if (e instanceof MissingServletRequestParameterException) {
            log.error("Missing request parameter:", e);
            MissingServletRequestParameterException ex = (MissingServletRequestParameterException) e;
            return ResponseVO.failure(ResponseStatusEnum.BAD_REQUEST.getCode(), "请求参数缺少: " + ex.getParameterName());
        } else if (e instanceof MethodArgumentTypeMismatchException) {
            log.error("Parameter type mismatch:", e);
            MethodArgumentTypeMismatchException ex = (MethodArgumentTypeMismatchException) e;
            return ResponseVO.failure(ResponseStatusEnum.BAD_REQUEST.getCode(), "请求参数类型不正确:" + ex.getName());
        } else if (e instanceof NoHandlerFoundException) {
            NoHandlerFoundException ex = (NoHandlerFoundException) e;
            log.error("No handler found:", e);
            return ResponseVO.failure(ResponseStatusEnum.NOT_EXIST, ex.getRequestURL());
        } else {
            log.error("【系统异常】", e);
            return ResponseVO.failure(ResponseStatusEnum.SYSTEM_ERROR.getCode(), ResponseStatusEnum.SYSTEM_ERROR.getMsg());
        }
    }
}

The handler is also registered as a bean:

@Bean
public GlobalExceptionHandler globalExceptionHandler() {
    return new GlobalExceptionHandler();
}

Conclusion

By defining a generic ResponseVO, a custom ResponseResultBodyAdvice with @ResponseResultBody, and a global exception handler, Spring Boot services can consistently return structured JSON and log errors uniformly, improving code maintainability and service robustness.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Spring Bootglobal exception handlingResponseBodyAdviceRestControllerAdviceResponseVO
Shepherd Advanced Notes
Written by

Shepherd Advanced Notes

Dedicated to sharing advanced Java technical insights, daily work snippets, and the power of persistent effort.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.