Spring Boot bài 22: Validation API, ProblemDetail và hợp đồng lỗi
- 18-10-2026
- Toanngo92
- 0 Comments
Giáo trình Spring Boot MVC và REST API · Chương 9/12: REST API và hợp đồng HTTP · Bài 22/28 · Biên soạn tháng 10/2026.
Mục lục
Mục tiêu bài học
Trả lỗi JSON có cấu trúc, phân biệt lỗi đầu vào với lỗi không tồn tại và tránh lộ thông tin hệ thống.
Client cần lỗi có thể xử lý bằng chương trình
Một stack trace HTML không phù hợp với ứng dụng đang chờ JSON. Spring hỗ trợ ProblemDetail để biểu diễn lỗi theo chuẩn Problem Details for HTTP APIs. Các trường quen thuộc gồm type, title, status, detail và instance; có thể thêm thuộc tính mở rộng khi cần.
ApiErrors là @RestControllerAdvice chỉ áp dụng cho CourseApiController, để lỗi giao diện vẫn được render thành trang HTML. CourseNotFound đổi thành 404, dữ liệu không hợp lệ thành 400 và vi phạm toàn vẹn dữ liệu thành 409. Không dùng cùng một chuỗi “có lỗi xảy ra” cho mọi trường hợp mà bỏ mất ý nghĩa của HTTP.
Tệp trong mã nguồn mẫu: src/main/java/com/hocvietcode/academy/ApiErrors.java. Đoạn dưới được trích từ tệp này; xem bản tải về để có đủ import và phần còn lại.
package com.hocvietcode.academy;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.http.*;
import org.springframework.dao.DataIntegrityViolationException;
@RestControllerAdvice(assignableTypes=CourseApiController.class)
public class ApiErrors {
@ExceptionHandler(CourseNotFound.class) ProblemDetail missing(CourseNotFound e){return ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND,e.getMessage());}
@ExceptionHandler(IllegalArgumentException.class) ProblemDetail invalid(IllegalArgumentException e){return ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST,e.getMessage());}
@ExceptionHandler(MethodArgumentNotValidException.class) ProblemDetail validation(MethodArgumentNotValidException e){
ProblemDetail p=ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST,"Dữ liệu đầu vào không hợp lệ");
p.setProperty("errors",e.getBindingResult().getFieldErrors().stream().map(x->x.getField()+": "+x.getDefaultMessage()).toList());return p;
}
@ExceptionHandler(DataIntegrityViolationException.class) ProblemDetail conflict(){return ProblemDetail.forStatusAndDetail(HttpStatus.CONFLICT,"Dữ liệu đang được sử dụng hoặc bị trùng");}
}
Thông báo theo trường và giới hạn thông tin
MethodArgumentNotValidException chứa các lỗi Bean Validation. Mẫu đưa chúng vào thuộc tính errors dưới dạng danh sách thông báo field: message. Một API lớn có thể dùng cấu trúc gồm field, code và message riêng để frontend dễ dịch và gắn đúng ô nhập; hãy giữ nhất quán trong cùng hợp đồng.
Không trả rejectedValue của trường nhạy cảm, câu SQL, tên bảng nội bộ hoặc exception.getStackTrace cho client. Lỗi DB trong mẫu được đổi thành mô tả trung tính. Phần chi tiết phục vụ vận hành nằm trong log được kiểm soát, không nằm trong response công khai.
Những lỗi không phải do @Valid
JSON sai cú pháp hoặc sai kiểu dữ liệu có thể gây HttpMessageNotReadableException trước khi validation chạy. Path/query sai kiểu gây lỗi chuyển đổi. Ứng dụng có thể mở rộng advice để chuẩn hóa các trường hợp đó; mẫu chỉ bảo đảm các nhánh lỗi đã được mô tả và kiểm thử, không tuyên bố mọi response lỗi của hệ thống đều có cùng envelope.
401/403 phát sinh ở Security filter thường không đi qua ControllerAdvice. Muốn hợp đồng lỗi JSON thống nhất cả tầng Security, cần AuthenticationEntryPoint và AccessDeniedHandler riêng. Đây là bước mở rộng có chủ đích, không phải bắt Exception chung trong Controller.
Ghi hợp đồng trước khi có frontend riêng
Với mỗi endpoint, ghi method, URL, query/body, quyền, CSRF, response thành công và lỗi có thể dự đoán. Có thể lưu mô tả bằng OpenAPI khi mở rộng; không cài thư viện Swagger/Springfox cũ của Boot 2 vào Boot 4 mà chưa kiểm tra tương thích. Bản mẫu chưa cài Swagger UI, nên không có đường dẫn giao diện tài liệu tự sinh.
Kiểm tra ít nhất JSON hợp lệ, tên trống, giá âm, danh mục không tồn tại và id không tồn tại. Test cần xác nhận status và Content-Type: application/problem+json, không chỉ tìm chữ lỗi trong body. Khi thay cấu trúc errors sau này, cập nhật test và client đồng thời.
Lỗi thường gặp và lưu ý
- Không trả lỗi nghiệp vụ bằng HTTP 200 chỉ để frontend khỏi vào nhánh catch.
- ControllerAdvice không tự chuẩn hóa lỗi do mọi filter hoặc web server tạo ra.
Ghi nhớ
Hợp đồng lỗi rõ giúp frontend phản hồi đúng và giữ chi tiết hệ thống ở nơi phù hợp.

