Say Goodbye to if‑else: Elegant Parameter Validation in Spring Boot
This article walks through why manual if‑else checks are cumbersome for API input validation, introduces Java Bean Validation (JSR‑303/349/380) and its implementations, shows how to use @Validated and @Valid annotations in Spring Boot, and demonstrates unified exception handling for clean error responses.
Overview
When building reliable APIs, validating request parameters is essential to guarantee correct data persistence. The author illustrates a typical menu‑creation validation method that uses dozens of if‑else statements, which quickly becomes unreadable and hard to maintain.
Bean Validation and Implementations
Java introduced the Bean Validation specification in 2009 (JSR‑303) and has since evolved through JSR‑349 and JSR‑380 (2.0). The two most common implementations are Hibernate Validator and Apache BVal . Although Hibernate is often associated with JPA, it also provides a full Bean Validation engine. The article shows Google Trends graphs comparing the popularity of MyBatis, JPA, and Hibernate in China and worldwide, and explains that Spring Boot already pulls in spring-boot-starter-validation, which transitively includes hibernate-validator.
Annotations
The javax.validation.constraints package defines 22 constraint annotations. The author groups them as follows: @NotBlank, @NotEmpty, @NotNull, @Null – null and emptiness checks. @DecimalMax, @DecimalMin, @Digits, @Positive, @PositiveOrZero, @Max, @Min, @Negative, @NegativeOrZero – numeric range checks. @AssertFalse, @AssertTrue – boolean checks. @Size – collection or string length. @Future, @FutureOrPresent, @Past, @PastOrPresent – date checks.
Hibernate‑specific constraints such as @Range, @Length, @URL, @SafeHtml.
Quick Start
First, add the required Maven dependencies (Spring Boot starter web already brings in validation):
<project xmlns="http://maven.apache.org/POM/4.0.0" ...>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.4.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- other optional dependencies such as lombok, knife4j, etc. -->
</dependencies>
</project>Define a DTO with validation annotations:
package com.ratel.validation.entity;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;
@Data
public class UserAddDTO {
@NotEmpty(message = "登录账号不能为空")
@Length(min = 5, max = 16, message = "账号长度为 5-16 位")
@Pattern(regexp = "^[A-Za-z0-9]+$", message = "账号格式为数字以及字母")
private String username;
@NotEmpty(message = "密码不能为空")
@Length(min = 4, max = 16, message = "密码长度为 4-16 位")
private String password;
}Apply validation in a controller. The class is annotated with @Validated so that all method parameters are checked. For complex objects, use @Valid on the parameter:
package com.ratel.validation.cotroller;
import com.ratel.validation.entity.UserAddDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.Min;
@RestController
@RequestMapping("/users")
@Validated
public class UserController {
private Logger logger = LoggerFactory.getLogger(getClass());
@GetMapping("/get")
public UserAddDTO get(@RequestParam("id") @Min(value = 1L, message = "编号必须大于 0") Integer id) {
logger.info("[get][id: {}]", id);
UserAddDTO dto = new UserAddDTO();
dto.setUsername("张三");
dto.setPassword("123456");
return dto;
}
@PostMapping("/add")
public void add(@Valid @RequestBody UserAddDTO addDTO) {
logger.info("[add][addDTO: {}]", addDTO);
}
}Run the application and use Swagger (e.g., http://localhost:8080/doc.html#/home) to test the endpoints. Invalid inputs produce detailed error messages.
Differences Between @Valid and @Validated
@Validatedis a Spring‑specific annotation that enables method‑level validation via AOP (using MethodValidationInterceptor). It validates simple parameters directly, so an int or String argument does not need @Valid. @Valid is the standard Bean Validation annotation; it triggers nested validation of object graphs. When a controller method receives a POJO (e.g., UserAddDTO), @Valid ensures each field’s constraints are checked.
Exception Handling
The article provides a global @ControllerAdvice that converts validation failures into a unified JSON response ( CommonResult). It handles: MissingServletRequestParameterException → error code MISSING_REQUEST_PARAM_ERROR. ConstraintViolationException (triggered by @Validated) → error code INVALID_REQUEST_PARAM_ERROR with concatenated messages. BindException and MethodArgumentNotValidException (triggered by @Valid) → same error code with detailed messages.
Any other Exception → generic system error.
@ControllerAdvice(basePackages = "com.ratel.validation.cotroller")
public class GlobalExceptionHandler {
private Logger logger = LoggerFactory.getLogger(getClass());
@ResponseBody
@ExceptionHandler(MissingServletRequestParameterException.class)
public CommonResult missingServletRequestParameterExceptionHandler(HttpServletRequest req, MissingServletRequestParameterException ex) {
logger.error("[missingServletRequestParameterExceptionHandler]", ex);
return CommonResult.error(ServiceExceptionEnum.MISSING_REQUEST_PARAM_ERROR.getCode(),
ServiceExceptionEnum.MISSING_REQUEST_PARAM_ERROR.getMessage());
}
@ResponseBody
@ExceptionHandler(ConstraintViolationException.class)
public CommonResult constraintViolationExceptionHandler(HttpServletRequest req, ConstraintViolationException ex) {
logger.error("[constraintViolationExceptionHandler]", ex);
StringBuilder detailMessage = new StringBuilder();
for (ConstraintViolation<?> cv : ex.getConstraintViolations()) {
if (detailMessage.length() > 0) detailMessage.append(";");
detailMessage.append(cv.getMessage());
}
return CommonResult.error(ServiceExceptionEnum.INVALID_REQUEST_PARAM_ERROR.getCode(),
ServiceExceptionEnum.INVALID_REQUEST_PARAM_ERROR.getMessage() + ":" + detailMessage.toString());
}
// BindException and MethodArgumentNotValidException handlers omitted for brevity
@ResponseBody
@ExceptionHandler(Exception.class)
public CommonResult exceptionHandler(HttpServletRequest req, Exception e) {
logger.error("[exceptionHandler]", e);
return CommonResult.error(ServiceExceptionEnum.SYS_ERROR.getCode(),
ServiceExceptionEnum.SYS_ERROR.getMessage());
}
}Testing the Unified Response
Calling GET /users/get?id=-1 returns a 500 status because ConstraintViolationException is not specially handled. Calling POST /users/add with an invalid JSON body (e.g., username "33" and password "233") returns a 400 status and a JSON payload where the errors field contains concise messages such as “账号长度为 5-16 位” and “密码长度为 4-16 位”. The global handler concatenates these messages into a single readable string.
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.
Architect's Guide
Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.
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.
