Spring Boot로 웹을 개발하면서 에러 처리 메커니즘에 관해 궁금증이 생겼다. 특히 AJAX 요청과 일반 브라우저 요청에서 에러 처리가 다르게 동작하는 방식을 이해하고 싶어 알아보기 시작했다.
이 글에서는 그러한 경험을 바탕으로 Spring의 기본 에러 처리 방식을 작성하여 기록하고자 한다.
개발과정에서 특정 500 에러가 발생하였는데, 이를 Handler에도 정의를 안해놓는 경험이 있다. 이 때 이 에러는 어떻게 처리가 되는지에 대해 의문을 가졌고 알아보기 시작했다.
확인한 결과, Spring Boot는 기본적으로 모든 오류를 /error 경로로 매핑한다.
이는 서블릿 컨테이너에 "전역" 오류 페이지로 등록된다. 코드를 분석해보니 이 처리는 BasicErrorController라는 클래스를 통해 이루어진다는 점을 확인하였다.
@Controller @RequestMapping("${server.error.path:${error.path:/error}}") public class BasicErrorController implements ErrorController { // ... }
실제로 500 에러와 같은 서버 내부 오류가 발생했을 때, 기본적으로 HTML 페이지나 JSON 형태로 에러 정보가 반환되는 것을 확인하였다.
이 과정에서 스프링이 클라이언트의 요청 유형을 감지하여 응답 형식을 다르게 처리한다는 점도 알게 되었다.
개발 중 테스트를 통해 Spring Boot가 요청하는 클라이언트의 유형에 따라 다르게 에러를 처리한다는 것을 확인하였다.
HTML을 요청하는 일반 브라우저 요청의 경우, 세부 에러 정보들을 포함한 HTML 형식의 에러 페이지가 반환되었다.
내부적으로 /error 경로로 리다이렉트되는 것을 확인할 수 있었다.
JSON/XML 등을 요청하는 방식의 경우, 에러 세부 정보와 HTTP 상태가 포함된 JSON 응답이 생성되었다.
리다이렉트 없이 API에 대한 응답으로 에러 정보가 반환되는 것을 확인하였다.
Accept: application/json 헤더가 이러한 처리를 가능하게 한다.
디버깅을 통해 스프링이 /error 경로로 요청을 디스패치하는 과정을 확인했다.
이는 실제 HTTP 요청을 보내는 것이 아니라 내부적으로 처리되는 방식으로, RequestDispatcher를 사용하여 요청을 다른 핸들러로 전달한다.
RequestDispatcher dispatcher = request.getRequestDispatcher("/error"); dispatcher.forward(request, response); // 기존 요청을 "/error" 핸들러로 전달
프로젝트 개발 중 Pathvariable URL 패턴을 사용하는 컨트롤러와 에러 처리 간의 충돌하는 문제가 있었다.
@GetMapping("/{path}") public ResponseEntity<?> handlePath(@PathVariable String path) { // ... }
이런 패턴이 있을 때 AJAX 요청에서 오류가 발생하면, 에러 처리가 BasicErrorController로 제대로 되지 않는 현상이 있었다.
이 문제를 해결하기 위해 아래와 같이 시도해보았다.
1. RestControllerAdvice에 Exception.class 추가
가장 효과적이었던 방법으로, 모든 예외를 캐치하여 일관된 방식으로 처리할 수 있었다.
@RestControllerAdvice @Order(Ordered.HIGHEST_PRECEDENCE) public class RestControllerExceptionHandler { //... @ExceptionHandler(Exception.class) public ResponseEntity<Object> handleAllExceptions(Exception ex, WebRequest request) { Map<String, Object> body = new HashMap<>(); body.put("message", ex.getMessage()); body.put("timestamp", new Date()); body.put("status", HttpStatus.INTERNAL_SERVER_ERROR.value()); return new ResponseEntity<>(body, HttpStatus.INTERNAL_SERVER_ERROR); } }
@GetMapping("/category/{path}") // '/{path}' 대신 더 구체적인 패턴
Spring Boot의 기본 에러 처리 메커니즘을 이해함으로써, 다양한 상황에서 발생할 수 있는 예상치 못한 동작을 처리할 수 있게 되었다.
특히 AJAX 요청과 광범위한 URL 패턴을 함께 사용할 때의 문제점을 이해했고 이에 대응하는 방법을 알았다.
실제 프로덕션 환경에서는 반드시 사용자 정의 예외 핸들러를 구현하여 로깅, 모니터링, 오류 메시지 등의 요구사항을 충족시키는 것이 바람직하다는 결론을 내렸다.
프로젝트를 통해 AJAX 요청에서는 /error로 리다이렉션되지 않고 JSON 형식으로 에러 응답을 받게 된다는 점을 활용하여, 클라이언트에서 더 적절하게 에러를 처리하고 사용자 경험을 개선할 수 있었다.