0

[Java Backend Zero to Hello] [Phase 5] BÀI 5.8: EXCEPTION HANDLING

📚 Series: Java Backend Zero to Hello 📂 Phân đoạn: Phase 5: Spring Framework & Spring Boot 📖 Nội dung: BÀI 5.8: EXCEPTION HANDLING 💡 Khóa học lập trình Backend Java & Spring Boot chuẩn doanh nghiệp từ con số 0.


BÀI 5.8: EXCEPTION HANDLING

Mục tiêu

  • Tạo Custom Exception
  • Sử dụng @ControllerAdvice
  • Xử lý các loại exception
  • Trả về ErrorResponse chuẩn

1. HỆ THỐNG EXCEPTION

Throwable
├── Error (JVM, không xử lý)
└── Exception
    ├── IOException (Checked)
    ├── SQLException (Checked)
    └── RuntimeException (Unchecked)
        ├── NullPointerException
        ├── IllegalArgumentException
        └── Custom Exception

2. CUSTOM EXCEPTION

2.1 Business Exception

public class NotFoundException extends RuntimeException {
    public NotFoundException(String message) {
        super(message);
    }
}

public class DuplicateException extends RuntimeException {
    public DuplicateException(String message) {
        super(message);
    }
}

public class BusinessException extends RuntimeException {
    private final String code;

    public BusinessException(String code, String message) {
        super(message);
        this.code = code;
    }

    public String getCode() {
        return code;
    }
}

2.2 Error Code Enum

public enum ErrorCode {
    USER_NOT_FOUND("USER_001", "Không tìm thấy user"),
    DUPLICATE_EMAIL("USER_002", "Email đã tồn tại"),
    INVALID_INPUT("INPUT_001", "Dữ liệu không hợp lệ"),
    UNAUTHORIZED("AUTH_001", "Chưa xác thực"),
    FORBIDDEN("AUTH_002", "Không có quyền truy cập"),
    INTERNAL_ERROR("SYS_001", "Lỗi hệ thống");

    private final String code;
    private final String message;

    ErrorCode(String code, String message) {
        this.code = code;
        this.message = message;
    }

    public String getCode() { return code; }
    public String getMessage() { return message; }
}

3. ERROR RESPONSE

public record ErrorResponse(
    String code,
    String message,
    Object details,
    LocalDateTime timestamp,
    String path
) {
    public static ErrorResponse of(ErrorCode errorCode, String path) {
        return new ErrorResponse(
            errorCode.getCode(),
            errorCode.getMessage(),
            null,
            LocalDateTime.now(),
            path
        );
    }

    public static ErrorResponse of(ErrorCode errorCode, Object details, String path) {
        return new ErrorResponse(
            errorCode.getCode(),
            errorCode.getMessage(),
            details,
            LocalDateTime.now(),
            path
        );
    }
}

4. @RESTCONTROLLERADVICE

4.1 Global Exception Handler

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    @ExceptionHandler(NotFoundException.class)
    public ResponseEntity<ErrorResponse> handleNotFound(
            NotFoundException ex, HttpServletRequest request) {
        log.warn("Not found: {}", ex.getMessage());
        return ResponseEntity.status(HttpStatus.NOT_FOUND)
            .body(ErrorResponse.of(ErrorCode.USER_NOT_FOUND, request.getRequestURI()));
    }

    @ExceptionHandler(DuplicateException.class)
    public ResponseEntity<ErrorResponse> handleDuplicate(
            DuplicateException ex, HttpServletRequest request) {
        log.warn("Duplicate: {}", ex.getMessage());
        return ResponseEntity.status(HttpStatus.CONFLICT)
            .body(ErrorResponse.of(ErrorCode.DUPLICATE_EMAIL, request.getRequestURI()));
    }

    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<ErrorResponse> handleBusiness(
            BusinessException ex, HttpServletRequest request) {
        log.warn("Business error: {}", ex.getMessage());
        return ResponseEntity.status(HttpStatus.BAD_REQUEST)
            .body(new ErrorResponse(
                ex.getCode(),
                ex.getMessage(),
                null,
                LocalDateTime.now(),
                request.getRequestURI()
            ));
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleValidation(
            MethodArgumentNotValidException ex, HttpServletRequest request) {
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getFieldErrors()
            .forEach(fe -> errors.put(fe.getField(), fe.getDefaultMessage()));

        return ResponseEntity.badRequest()
            .body(ErrorResponse.of(ErrorCode.INVALID_INPUT, errors, request.getRequestURI()));
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleGeneric(
            Exception ex, HttpServletRequest request) {
        log.error("Unexpected error", ex);
        return ResponseEntity.internalServerError()
            .body(ErrorResponse.of(ErrorCode.INTERNAL_ERROR, request.getRequestURI()));
    }
}

5. XỬ LÝ CÁC EXCEPTION CỤ THỂ

5.1 Validation

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidation(
        MethodArgumentNotValidException ex) {
    // Xử lý @Valid trên @RequestBody
}

@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<ErrorResponse> handleConstraint(
        ConstraintViolationException ex) {
    // Xử lý @Valid trên @PathVariable, @RequestParam
}

@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<ErrorResponse> handleNotReadable(
        HttpMessageNotReadableException ex) {
    // JSON không parse được
}

5.2 Database

@ExceptionHandler(DataIntegrityViolationException.class)
public ResponseEntity<ErrorResponse> handleDataIntegrity(
        DataIntegrityViolationException ex) {
    // Vi phạm constraint
}

@ExceptionHandler(OptimisticLockingFailureException.class)
public ResponseEntity<ErrorResponse> handleOptimisticLock(
        OptimisticLockingFailureException ex) {
    // Optimistic lock conflict
}

5.3 Security

@ExceptionHandler(AccessDeniedException.class)
public ResponseEntity<ErrorResponse> handleAccessDenied(
        AccessDeniedException ex) {
    return ResponseEntity.status(HttpStatus.FORBIDDEN)
        .body(ErrorResponse.of(ErrorCode.FORBIDDEN, null));
}

@ExceptionHandler(AuthenticationException.class)
public ResponseEntity<ErrorResponse> handleAuth(
        AuthenticationException ex) {
    return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
        .body(ErrorResponse.of(ErrorCode.UNAUTHORIZED, null));
}

6. SỬ DỤNG TRONG SERVICE

@Service
@RequiredArgsConstructor
public class UserService {

    private final UserRepository userRepository;

    public UserResponse getById(Long id) {
        User user = userRepository.findById(id)
            .orElseThrow(() -> new NotFoundException("User not found: " + id));
        return UserResponse.from(user);
    }

    public UserResponse create(CreateUserRequest request) {
        if (userRepository.existsByEmail(request.email())) {
            throw new DuplicateException("Email already exists: " + request.email());
        }
        // ...
    }
}

7. PROBLEM DETAILS (RFC 7807)

Spring 6 hỗ trợ chuẩn Problem Details.

@ExceptionHandler(NotFoundException.class)
public ProblemDetail handleNotFound(NotFoundException ex) {
    ProblemDetail problem = ProblemDetail.forStatusAndDetail(
        HttpStatus.NOT_FOUND, ex.getMessage()
    );
    problem.setTitle("Resource Not Found");
    problem.setType(URI.create("https://example.com/errors/not-found"));
    problem.setProperty("timestamp", Instant.now());
    return problem;
}

8. LOG EXCEPTION

@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGeneric(Exception ex,
                                                    HttpServletRequest request) {
    // Log đầy đủ thông tin
    log.error("Unexpected error at {}: {}",
        request.getRequestURI(), ex.getMessage(), ex);

    // Trả về thông tin chung cho client (không lộ chi tiết)
    return ResponseEntity.internalServerError()
        .body(ErrorResponse.of(ErrorCode.INTERNAL_ERROR, request.getRequestURI()));
}

9. CUSTOM ERROR CONTROLLER

@Controller
public class CustomErrorController implements ErrorController {

    @RequestMapping("/error")
    public ResponseEntity<ErrorResponse> handleError(HttpServletRequest request) {
        Integer status = (Integer) request.getAttribute("jakarta.servlet.error.status_code");
        // ...
    }
}

10. BÀI TẬP THỰC HÀNH

Bài 1: Custom Exceptions

Tạo các exception: NotFoundException, DuplicateException, BusinessException.

Bài 2: Global Handler

Implement GlobalExceptionHandler xử lý tất cả exception.

Bài 3: ErrorResponse

Tạo ErrorResponse chuẩn với code, message, details, timestamp.


11. TÓM TẮT

Khái niệm Mô tả
Custom Exception Kế thừa RuntimeException
ErrorCode Enum mã lỗi
ErrorResponse Format response lỗi
@RestControllerAdvice Global handler
@ExceptionHandler Xử lý exception cụ thể
ProblemDetail RFC 7807
Logging Log đầy đủ thông tin

Bài tiếp theo: 5.9 Spring Security cơ bản


🧭 Điều hướng bài học

⭐ Hãy bookmark (clip) lại series để tiện theo dõi các bài học tiếp theo nhé!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí