Missing Parameter Validation Can Crash a Spring Boot Service – A Complete Guide
A real‑world incident where an unchecked null request parameter caused a full‑table query, massive memory usage and OOM is examined, then Spring Boot’s validation framework—including manual checks, annotation‑driven constraints, nested and group validation, and global exception handling—is demonstrated step‑by‑step with code samples.
1. Background
A recent production outage occurred because a front‑end request sent an empty parameter. The back‑end used that parameter directly in a database query, which returned millions of rows, triggered frequent full GCs and eventually caused an OOM error, rendering the service unavailable. The incident highlighted the importance of treating every public API as an "open" interface that must be defensive, robust, and secure.
2. Manual Parameter Checks
Before adopting a framework, developers often write explicit if‑else checks. The article shows a controller method that validates fields such as userNo, name, phone, and gender with StringUtils.isBlank and throws a custom BizException when a check fails.
@RestController
@RequestMapping("/user")
public class UserController {
@Resource
private UserService userService;
@PostMapping
public void addUser(@RequestBody UserParam userParam) {
if (StringUtils.isBlank(userParam.getUserNo())) {
throw new BizException("用户名不能为空");
}
if (StringUtils.isBlank(userParam.getName())) {
throw new BizException("姓名不能为空");
}
if (StringUtils.isBlank(userParam.getPhone())) {
throw new BizException("手机号不能为空");
}
if (StringUtils.isBlank(userParam.getUserNo())) {
throw new BizException("账号不能为空");
}
if (Objects.isNull(userParam.getGender())) {
throw new BizException("性别不能为空");
}
}
}While functional, this approach leads to repetitive code, inconsistent error handling, and maintenance difficulties.
2.1 Introducing Spring Validator
Adding the spring-boot-starter-validation dependency enables the use of JSR‑303/JSR‑380 annotations. The following common constraint annotations are demonstrated: @NotNull – validates that the object is not null (any type). @NotBlank – validates that a String is not empty and contains at least one non‑whitespace character. @NotEmpty – validates that a collection/array/string is not null and not empty. @AssertTrue / @AssertFalse – validates a boolean value. @Size(min, max) – validates length of a String, collection, map or array. @Length(min, max) – Hibernate extension for string length. @Min(value) / @Max(value) – validates numeric minimum/maximum (inclusive). @DecimalMin(value) / @DecimalMax(value) – validates BigDecimal / String decimal bounds. @Digits(integer, fraction) – validates number of integer and fraction digits. @Positive / @PositiveOrZero – validates positive numbers. @Negative / @NegativeOrZero – validates negative numbers. @Past / @PastOrPresent / @Future / @FutureOrPresent – validates date/time constraints. @Email – validates email format. @Pattern(regex) – validates a string against a regular expression. @URL – validates URL format. @CreditCardNumber – validates credit‑card format using the Luhn algorithm. @Valid – triggers cascading validation on nested objects or collections. @SafeHtml – Hibernate extension to reject malicious HTML. @ScriptAssert – class‑level validation using a script expression.
2.2 Applying Validation to Controllers
Annotate the controller class with @Validated and place constraint annotations on method parameters. Example for a request body:
@PostMapping
public void addUser(@RequestBody @Validated UserParam userParam) {
System.out.println(userParam);
}When validation fails, Spring throws MethodArgumentNotValidException (for request bodies) or ConstraintViolationException (for request parameters). The article shows a Postman request that omits userNo, resulting in a 400 Bad Request response with a detailed error map.
Global Exception Handling
A @ControllerAdvice class can translate validation exceptions into a unified response format:
@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<>();
BindingResult result = ex.getBindingResult();
result.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 ConstraintViolationException) {
log.error("Data validation error:", e);
return ResponseVO.failure(ResponseStatusEnum.BAD_REQUEST, e.getMessage());
}
// other exception branches omitted for brevity
log.error("[System Exception]", e);
return ResponseVO.failure(ResponseStatusEnum.SYSTEM_ERROR.getCode(), ResponseStatusEnum.SYSTEM_ERROR.getMsg());
}
}After adding this handler, the same invalid request returns a concise JSON payload such as:
{
"code": 400,
"msg": "Bad Request",
"data": {"userNo": "用户名不能为空"}
}Validating @RequestParam and @PathVariable
For GET requests, place @Validated on the controller class and use constraint annotations directly on the method parameters:
@GetMapping("/{userId}")
public void detail(@PathVariable("userId") @Min(value = 1L, message = "userId必须大于0") Long userId) {
System.out.println(userId);
}
@GetMapping("/info")
public void getUserInfo(@RequestParam("userId") @Max(value = 10L, message = "userId必须不超过10") Long userId) {
System.out.println(userId);
}Validation failures again raise ConstraintViolationException which is handled by the global advice.
Nested Validation
When a DTO contains other objects or collections, annotate the field with @Valid (or @Validated on the containing class) to trigger cascading checks. Example:
@Data
public class UserParam {
@NotBlank(message = "用户名不能为空")
@Size(min = 8, max = 16, message = "长度必须在8~16个字符之间")
private String userNo;
@NotBlank(message = "姓名不能为空")
@Size(max = 32, message = "姓名不能超过32个字符")
private String name;
@NotNull(message = "性别不能为空")
private Integer gender;
@Past(message = "出身日期必须在当前日期之前")
private Date birthday;
@Email(message = "邮箱格式不对")
private String email;
@Pattern(regexp = "^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\\d{8}$", message = "手机号格式不对")
private String phone;
@NotEmpty(message = "地址不能为空")
@Valid
private List<Address> addressList;
}
@Data
public class Address {
@NotBlank(message = "省份不能为空")
private String province;
@NotBlank(message = "城市不能为空")
private String city;
private String region;
private String address;
}Submitting a payload that omits city yields an error entry like addressList[0].city: 城市不能为空.
Group Validation
Different API operations (create, update, delete) often require distinct validation rules. Spring’s @Validated supports validation groups. The article defines marker interfaces Insert and Update that extend a Default group, then applies them to fields:
@Data
public class UserParam {
@NotNull(message = "id不能为空", groups = {Update.class})
private Long id;
@NotBlank(message = "用户名不能为空", groups = {Insert.class})
@Size(min = 8, max = 16, message = "长度必须在8~16个字符之间")
private String userNo;
// other fields omitted for brevity
@NotEmpty(message = "地址不能为空")
@Valid
private List<Address> addressList;
}
public interface Insert {}
public interface Update {}Controller methods then specify the group to activate:
@PostMapping
public void addUser(@RequestBody @Validated({UserParam.Insert.class}) UserParam userParam) {
System.out.println(userParam);
}
@PutMapping
public void updateUser(@RequestBody @Validated(UserParam.Update.class) UserParam userParam) {
System.out.println(userParam);
}If the group definitions do not extend Default, fields without an explicit group are ignored, which explains why some fields stopped being validated in the article’s experiment. Extending Default ensures that common constraints are always applied.
3. Summary
Spring Boot provides a powerful, annotation‑driven validation mechanism that reduces boilerplate and improves API robustness.
Use @Validated on controllers and constraint annotations on DTO fields to enforce rules automatically.
Handle validation failures centrally with a @ControllerAdvice to return consistent error responses.
Leverage @Valid for nested object validation and validation groups for operation‑specific rules.
Place validation logic in DTOs to keep business code clean and maintainable.
Future articles will explore custom validators, internationalized messages, and advanced use cases.
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.
Shepherd Advanced Notes
Dedicated to sharing advanced Java technical insights, daily work snippets, and the power of persistent effort.
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.
