Unify Spring Boot API Responses and Streamline Exception Handling

This article explains how to standardize API return formats with a generic Result object, define error codes and custom exceptions, implement a global exception handler, use ResponseBodyAdvice for automatic response wrapping, and package these utilities into a reusable Spring Boot starter for cleaner backend code.

Su San Talks Tech
Su San Talks Tech
Su San Talks Tech
Unify Spring Boot API Responses and Streamline Exception Handling

In Domain‑Driven Design (DDD) the interface layer handles external requests and returns results.

1. Unified Return Format

1.1 Build Result object

To standardize responses a generic Result<T> class is introduced with fields code, message, data and timestamp.

@Data
@Accessors(chain = true)
public class Result<T> {
    public static final String SUCCESS_CODE = "OK";
    private String code;
    private String message;
    private T data;
    private long timestamp;
}

A helper ResultHelper provides static methods to create success or failure results.

@Slf4j
public class ResultHelper {

    public static <T> Result<T> success(T data) {
        return new Result<T>()
                .setCode(SUCCESS_CODE)
                .setData(data)
                .setTimestamp(System.currentTimeMillis());
    }

    public static <T> Result<T> fail(String message) {
        return new Result<T>()
                .setCode(ErrorCode.SERVICE_ERROR.getCode())
                .setMessage(message)
                .setTimestamp(System.currentTimeMillis());
    }
    // ...
}

1.2 Optimize DailyMart API

Example registration endpoint returns Result<UserRegistrationDTO> using the helper.

@PostMapping("/api/customer/register")
public Result<UserRegistrationDTO> register(@RequestBody @Valid UserRegistrationDTO customerDTO) {
    try {
        return ResultHelper.success(customerService.register(customerDTO));
    } catch (Exception e) {
        return ResultHelper.fail(e.getMessage());
    }
}

Successful response yields JSON with code "OK", message null, data object and timestamp; failure returns error code such as "B0001" and message.

2. Exception Control

Instead of throwing generic RuntimeException, custom exceptions derived from an abstract base provide error codes and messages.

2.1 Error Code Concept

Following Alibaba development guidelines, error codes consist of a source identifier (A‑user, B‑system, C‑third‑party) and a four‑digit number.

A indicates user‑related errors.

B indicates system errors.

C indicates third‑party service errors.

2.2 Define ErrorCode enum

public enum ErrorCode {
    OK("00000","操作已成功"),
    CLIENT_ERROR("A0001","客户端错误"),
    USER_NOT_FOUND("A0010","用户不存在"),
    USER_ALREADY_EXISTS("A0011","用户已存在"),
    USERNAME_PASSWORD_INCORRECT("A0012","用户名或密码错误"),
    VERIFICATION_CODE_EXPIRED("A0013","验证码已过期"),
    BAD_CREDENTIALS_EXPIRED("A0014","用户认证异常"),
    SERVICE_ERROR("B0001","系统内部错误"),
    SERVICE_TIMEOUT_ERROR("B0010","系统执行超时"),
    REMOTE_ERROR("C0001","第三方服务错误");
    // fields, constructor, getters ...
}

2.3 Create custom exception hierarchy

AbstractException stores code and message and extends RuntimeException. Specific exceptions such as ClientException, BusinessException, and RemoteException inherit from it.

@Getter
public abstract class AbstractException extends RuntimeException {
    private final String code;
    private final String message;

    public AbstractException(ErrorCode errorCode, String message, Throwable throwable) {
        super(message, throwable);
        this.code = errorCode.getCode();
        this.message = Optional.ofNullable(message).orElse(errorCode.getMessage());
    }
}

Example concrete exception:

public class ClientException extends AbstractException {

    public ClientException() {
        this(ErrorCode.CLIENT_ERROR, null, null);
    }

    public ClientException(String message) {
        this(ErrorCode.CLIENT_ERROR, message, null);
    }
    // ... other constructors ...
}

Specific UserNotFoundException extends ClientException and uses ErrorCode.USER_NOT_FOUND.

3. Global Exception Handling

Using @RestControllerAdvice a GlobalExceptionHandler handles validation errors, custom AbstractException, and any other Throwable, returning a unified Result object.

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<Void> handleValidException(HttpServletRequest request, MethodArgumentNotValidException ex) {
        // extract first field error message
        String msg = ...;
        log.error("[{}] {} [ex] {}", request.getMethod(), getUrl(request), msg);
        return ResultHelper.fail(ErrorCode.CLIENT_ERROR, msg);
    }

    @ExceptionHandler(AbstractException.class)
    public Result<Void> handleAbstractException(HttpServletRequest request, AbstractException ex) {
        log.error("[{}] {} [ex] {}", request.getMethod(), getUrl(request), ex.toString());
        return ResultHelper.fail(ex);
    }

    @ExceptionHandler(Throwable.class)
    public Result<Void> handleThrowable(HttpServletRequest request, Throwable throwable) {
        log.error("[{}] {}", request.getMethod(), getUrl(request), throwable);
        return ResultHelper.fail();
    }
}

4. Automatic Response Wrapping

Implementing ResponseBodyAdvice allows all controller return values to be automatically wrapped with ResultHelper.success(), eliminating repetitive code.

@Slf4j
@RestControllerAdvice
public class GlobalResponseBodyAdvice implements ResponseBodyAdvice<Object> {

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
                                Class<? extends HttpMessageConverter<?>> selectedConverterType,
                                ServerHttpRequest request, ServerHttpResponse response) {
        if (body instanceof String) {
            return objectMapper.writeValueAsString(ResultHelper.success(body));
        }
        if (body instanceof Result) {
            return body;
        }
        return ResultHelper.success(body);
    }
}

5. Defining a Spring Boot Starter

To reuse the global exception handler and response‑body advice across services, they are packaged into a starter with an auto‑configuration class WebAutoConfiguration that registers the beans if they are missing.

@SpringBootConfiguration
@ConditionalOnWebApplication
public class WebAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean(GlobalExceptionHandler.class)
    public GlobalExceptionHandler dailyMartGlobalExceptionHandler() {
        return new GlobalExceptionHandler();
    }

    @Bean
    @ConditionalOnMissingBean(GlobalResponseBodyAdvice.class)
    public GlobalResponseBodyAdvice dailyMartGlobalResponseBodyAdvice() {
        return new GlobalResponseBodyAdvice();
    }
}

The starter also includes the required META-INF/spring/AutoConfiguration.imports file so that Spring Boot loads the configuration automatically.

Conclusion

The article demonstrates how to standardize API responses, define error codes, create custom exceptions, implement a global exception handler, use ResponseBodyAdvice for automatic wrapping, and package these utilities into a reusable Spring Boot starter, thereby simplifying controller code and improving maintainability.

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 BootresponsebodyadviceResult WrapperStarterGlobal Exception
Su San Talks Tech
Written by

Su San Talks Tech

Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.cn.

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.