[JAVA] Spring Boot 예외 처리 완전 정복: @ExceptionHandler와 @ControllerAdvice의 활용

실제 서비스를 운영하다 보면 에러는 언제든 발생할 수 있습니다. 이때 중요한 건 단순히 에러가 발생하는 것이 아니라, 에러를 어떻게 안정적으로 처리하고 사용자에게 의미 있는 메시지를 전달할 것인가입니다.

Spring Boot에서는 @ExceptionHandler@ControllerAdvice를 이용해 전역 예외 처리(글로벌 에러 핸들링)을 우아하게 구현할 수 있습니다. 예제 중심으로 그 개념과 활용법을 정리해 보겠습니다.

 

1. 예외 처리를 왜 분리해야 하는가?

  • 컨트롤러 로직과 예외 로직의 분리로 가독성 향상
  • API 응답을 일관된 형식(JSON)으로 유지
  • 에러 로깅을 한 곳에서 관리 가능

 

2. @ExceptionHandler 기본 사용법

특정 컨트롤러 내에서 발생한 예외를 처리하고 싶을 때는 해당 컨트롤러 클래스 안에 @ExceptionHandler를 정의할 수 있습니다.


@RestController
@RequestMapping("/api/users")
public class UserController {

    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.findById(id)
            .orElseThrow(() -> new UserNotFoundException("사용자를 찾을 수 없습니다."));
    }

    @ExceptionHandler(UserNotFoundException.class)
    public ResponseEntity<String> handleUserNotFound(UserNotFoundException ex) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage());
    }
}

 

3. @ControllerAdvice로 전역 예외 처리하기

프로젝트 전체에서 발생하는 공통 예외를 처리하고 싶다면 @ControllerAdvice를 활용하세요.


@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(UserNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleUserNotFound(UserNotFoundException ex) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND)
                .body(new ErrorResponse("USER_NOT_FOUND", ex.getMessage()));
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleValidation(MethodArgumentNotValidException ex) {
        String errorMessage = ex.getBindingResult()
                                .getFieldErrors()
                                .stream()
                                .map(error -> error.getField() + ": " + error.getDefaultMessage())
                                .collect(Collectors.joining(", "));
        return ResponseEntity.badRequest()
                .body(new ErrorResponse("VALIDATION_ERROR", errorMessage));
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleGeneral(Exception ex) {
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(new ErrorResponse("INTERNAL_ERROR", "서버 오류가 발생했습니다."));
    }
}

 

※ ErrorResponse 클래스 예시


public class ErrorResponse {
    private String code;
    private String message;

    // 생성자, getter/setter 생략
}

 

4. 예외 커스터마이징 실무 팁

  • 비즈니스 예외: 도메인 중심의 예외(UserNotFoundException 등)를 정의해 구체적인 오류 표현
  • 공통 응답 포맷: 모든 오류를 같은 구조로 내려줘야 API 소비자가 해석하기 쉬움
  • 로깅 전략: log.error()는 꼭 남겨두세요. 서버 모니터링 시 매우 유용합니다.

 

5. 실무에서 많이 사용하는 예외 핸들러

예외 클래스 처리 목적
IllegalArgumentException 잘못된 파라미터
MethodArgumentNotValidException Validation 실패
HttpRequestMethodNotSupportedException 지원하지 않는 HTTP 메서드
MissingServletRequestParameterException 필수 파라미터 누락
Exception 기타 모든 예외 (fallback)

 

6. 결론

Spring에서의 예외 처리는 단순히 에러를 잡는 것 이상입니다. 서비스 품질, 디버깅 효율성, 사용자 경험 모두에 직결되는 요소이기 때문에, 처음부터 제대로 설계하고 구성하는 것이 중요합니다.

@ExceptionHandler@ControllerAdvice를 잘 활용하면 예외 처리의 일관성과 유지보수성이 크게 향상됩니다.