Spring Boot API Validation, Global Exception Handling & Secure Versioning

This guide walks through building robust Spring Boot backend APIs by detailing request parameter validation with @Validator, implementing global exception handling, standardizing unified response structures, applying custom annotations for response control, managing API versioning via path and header strategies, and enhancing security with token authentication, timestamps, signatures, and HTTPS.

Architect's Guide
Architect's Guide
Architect's Guide
Spring Boot API Validation, Global Exception Handling & Secure Versioning

Overview

Backend API development with Spring Boot is divided into URL, request method, request data and response data. Standardization is essential.

Environment

Include spring-boot-starter-web, lombok, knife4j-spring-boot-starter and spring-boot-starter-validation dependencies.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-spring-boot-starter</artifactId>
    <version>2.0.2</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

Parameter Validation

Three approaches are discussed: service‑layer validation, Validator + BindingResult, and Validator + automatic exception. The article recommends the third method, where @Valid triggers a validation exception automatically.

@PostMapping("/addUser")
public String addUser(@RequestBody @Validated User user, BindingResult bindingResult) {
    List<ObjectError> allErrors = bindingResult.getAllErrors();
    if (!allErrors.isEmpty()) {
        return allErrors.stream()
                .map(o -> o.getDefaultMessage())
                .collect(Collectors.toList())
                .toString();
    }
    return validationService.addUser(user);
}
@PostMapping("/addUser")
public String addUser(@RequestBody @Validated User user) {
    return validationService.addUser(user);
}
@Data
public class User {
    @NotNull(message = "用户id不能为空")
    private Long id;
    @NotNull(message = "用户账号不能为空")
    @Size(min = 6, max = 11, message = "账号长度必须是6-11个字符")
    private String account;
    @NotNull(message = "用户密码不能为空")
    @Size(min = 6, max = 16, message = "密码长度必须是6-16个字符")
    private String password;
    @NotNull(message = "用户邮箱不能为空")
    @Email(message = "邮箱格式不正确")
    private String email;
}

Global Exception Handling

Define a @RestControllerAdvice class with @ExceptionHandler methods for MethodArgumentNotValidException, BindException, custom APIException, and generic exceptions, returning a unified ResultVO object.

@RestControllerAdvice
public class ExceptionControllerAdvice {
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResultVO<String> handle(MethodArgumentNotValidException e) {
        List<String> msgs = e.getBindingResult().getFieldErrors()
                .stream()
                .map(DefaultMessageSourceResolvable::getDefaultMessage)
                .collect(Collectors.toList());
        return new ResultVO<>(ResultCode.VALIDATE_FAILED, msgs);
    }
    // other handlers omitted for brevity
}

Unified Response

Define ResultCode enum and ResultVO<T> class to wrap data, code and message. Controllers return ResultVO instead of raw objects.

public enum ResultCode {
    SUCCESS(1000, "操作成功"),
    FAILED(1001, "响应失败"),
    VALIDATE_FAILED(1002, "参数校验失败"),
    ERROR(5000, "未知错误");
    private final int code;
    private final String msg;
    ResultCode(int code, String msg) { this.code = code; this.msg = msg; }
}
public class ResultVO<T> {
    private int code;
    private String msg;
    private T data;
    public ResultVO(T data) { this(ResultCode.SUCCESS, data); }
    public ResultVO(ResultCode rc, T data) {
        this.code = rc.getCode();
        this.msg = rc.getMsg();
        this.data = data;
    }
}

Optional Response Wrapping

A custom annotation @NotResponseBody can be placed on methods to skip the global wrapper. A ResponseBodyAdvice implementation checks for this annotation.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface NotResponseBody { }
@RestControllerAdvice
public class ResponseControllerAdvice implements ResponseBodyAdvice<Object> {
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return !(returnType.getParameterType().equals(ResultVO.class) ||
                 returnType.hasMethodAnnotation(NotResponseBody.class));
    }
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
                                Class<? extends HttpMessageConverter<?>> selectedConverterType,
                                ServerHttpRequest request, ServerHttpResponse response) {
        if (returnType.getGenericParameterType().equals(String.class)) {
            try {
                return new ObjectMapper().writeValueAsString(new ResultVO<>(body));
            } catch (JsonProcessingException e) {
                throw new APIException("返回String类型错误");
            }
        }
        return new ResultVO<>(body);
    }
}

API Version Control

Two strategies: path‑based versioning using a custom @ApiVersion annotation and a PathVersionHandlerMapping, and header‑based versioning checking the X‑VERSION header.

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiVersion {
    String value() default "1.0";
}
public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> {
    private final String version;
    public ApiVersionCondition(String version) { this.version = version; }
    @Override
    public ApiVersionCondition combine(ApiVersionCondition other) { return new ApiVersionCondition(other.version); }
    @Override
    public ApiVersionCondition getMatchingCondition(HttpServletRequest request) {
        Matcher m = Pattern.compile("v(\\d+\\.\\d+)").matcher(request.getRequestURI());
        if (m.find() && Objects.equals(m.group(1), version)) { return this; }
        return null;
    }
    @Override
    public int compareTo(ApiVersionCondition other, HttpServletRequest request) { return 0; }
    public String getApiVersion() { return version; }
}
@Configuration
public class WebMvcConfiguration implements WebMvcRegistrations {
    @Override
    public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
        return new PathVersionHandlerMapping();
    }
}

API Security

Discuss token authentication, timestamp validation, URL signing, replay protection, and HTTPS. Token is generated as md5(userId + timestamp + secretKey) and stored in Redis. Each request includes token, timestamp and sign; the server verifies all three and caches the signature to prevent replay attacks.

Conclusion

The article assembles validation, global exception handling, unified responses, versioning and security into a coherent Spring Boot backend API framework, allowing developers to focus on business logic while maintaining consistency and safety.

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 BootAPI SecurityVersioningAPI Validationglobal exception handlingResponse Wrapping
Architect's Guide
Written by

Architect's Guide

Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.

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.