Vấn đề
Việc thiết lập validation dữ liệu trong Spring Boot lẽ ra phải rất đơn giản. Bạn thêm một vài annotation như @NotNull, gửi một request không hợp lệ và mong đợi nhận lại một thông báo lỗi rõ ràng. Tuy nhiên, thực tế bạn thường gặp khó khăn. API của bạn có thể trả về lỗi 400 Bad Request hỗn loạn với một stack trace khổng lồ trong log, hoặc tệ hơn là bỏ qua hoàn toàn các quy tắc validation của bạn.
Điều này xảy ra vì Spring ném ra một MethodArgumentNotValidException khi validation thất bại. Theo mặc định, Spring không biết bạn muốn hiển thị các lỗi này cho frontend như thế nào. Kết quả là bạn nhận được một phản hồi mặc định mà ứng dụng React hoặc Angular gần như không thể xử lý hiệu quả.
org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public org.springframework.http.ResponseEntity<?> ...
Tóm tắt: Danh sách 3 bước cần kiểm tra
- Kiểm tra Dependency: Spring Boot 2.3+ yêu cầu
spring-boot-starter-validation. Nó không còn được bao gồm sẵn trong web starter. - Kích hoạt kiểm tra: Bạn phải thêm
@Validhoặc@Validatedtrước@RequestBodytrong controller. - Định dạng đầu ra: Sử dụng
@RestControllerAdviceđể chuyển đổi exception thành một JSON map gọn gàng.
Bước 1: Xác minh các Dependency của bạn
Nếu các annotation validation của bạn đang bị bỏ qua, đây thường là nguyên nhân chính. Kể từ năm 2020, Spring Boot đã tách riêng logic validation để giảm kích thước file JAR mặc định khoảng 1MB.
Đối với người dùng Maven, hãy thêm nội dung này vào pom.xml của bạn:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Bước 2: Sử dụng Annotation đúng cách
Validation yêu cầu hai phần: định nghĩa các ràng buộc trên dữ liệu của bạn và yêu cầu controller thực thi chúng.
DTO (Data Transfer Object)
Lưu ý tên các package. Nếu bạn đang dùng Spring Boot 3.0 trở lên, hãy sử dụng jakarta.validation. Đối với các phiên bản cũ hơn, hãy sử dụng javax.validation.
public class UserRequest {
@NotBlank(message = "Username is required")
private String username;
@Email(message = "Please provide a valid email address")
private String email;
// Getters and Setters
}
Controller
Nếu không có annotation @Valid, Spring sẽ bỏ qua các quy tắc bạn vừa viết. Nó sẽ xử lý JSON gửi đến như một đối tượng thông thường mà không kiểm tra các trường bên trong.
@PostMapping("/users")
public ResponseEntity<String> createUser(@Valid @RequestBody UserRequest request) {
return ResponseEntity.ok("User is valid!");
}
Bước 3: Xử lý lỗi toàn cục
Khi validation thất bại, Spring sẽ dừng request và ném ra exception. Để ngăn stack trace bị rò rỉ tới người dùng, hãy tạo một handler toàn cục. Nó sẽ bắt lấy exception và trả về một cặp Key-Value đơn giản chứa các lỗi.
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidationExceptions(
MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage())
);
return ResponseEntity.badRequest().body(errors);
}
}
Nguyên nhân kỹ thuật cốt lõi
Spring Boot dựa trên Bean Validation API (JSR 380). Hibernate Validator đóng vai trò là engine thực thi ngầm bên dưới. Khi một request gửi đến endpoint của bạn, RequestResponseBodyMethodProcessor sẽ giải quyết tham số. Nếu nó thấy @Valid, nó sẽ gọi validator. Nếu có lỗi tồn tại, chúng sẽ được lưu trữ trong một đối tượng BindingResult, sau đó đối tượng này được bao bọc bên trong MethodArgumentNotValidException.
Kiểm tra kết quả
Khởi động Postman hoặc sử dụng curl để kiểm tra. Gửi một request với username trống:
curl -X POST http://localhost:8080/users \
-H "Content-Type: application/json" \
-d '{"username": "", "email": "not-an-email"}'
Kết quả:
Thay vì lỗi 500 Internal Server Error, giờ đây bạn nhận được phản hồi 400 gọn gàng:
{
"username": "Username is required",
"email": "Please provide a valid email address"
}
Các lỗi thường gặp cần tránh
- Đối tượng lồng nhau: Nếu DTO của bạn chứa một đối tượng khác (như
Address), bạn phải đặt@Validtrên chính trường đó. Nếu không, đối tượng bên trong sẽ không được kiểm tra. - Import sai thư viện: Việc trộn lẫn giữa
javaxvàjakartalà nguyên nhân phổ biến gây ra lỗi không thông báo trong các dự án Spring Boot 3. - Validation Groups: Nếu bạn cần các quy tắc khác nhau cho "Create" so với "Update", bạn phải sử dụng
@Validatedthay vì@Valid.

