BackEnd/Spring Boot

Spring Boot 예외 처리 전략 [Custom Exception, ExecptionHandler]

연향동큰손 2025. 10. 22. 16:01

Custom Exception

커스텀 예외란 자바의 기본 예외(RuntimeException, IllegalArgumentException 등)를 상속받아 비즈니스 로직에 맞는 의미 있는 예외 클래스를 직접 정의하는 것을 말한다.

 

자바에서 제공하는 기본 예외를 이용하여 try-catch문을 통해 예외 처리를 해줘도 오류 상황을 면할수는 있지만, 어떤 비즈니스 로직에서 어떤 원인으로 예외가 발생했는지 명확하게 표현하기 힘들다.

 

기본 예외를 서비스 계층에서 던지게 구현을 했다면 컨트롤러에서 try-catch문을 통해 모두 예외 상황에 맞는 HTTP 상태코드를 지정해줘야 한다.

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

    private final UserService userService;

    @GetMapping("/{id}")
    public ResponseEntity<?> getUser(@PathVariable Long id) {
        try {
            User user = userService.getUser(id);
            return ResponseEntity.ok(user);
        } catch (IllegalArgumentException e) {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                    .body(Map.of("message", e.getMessage()));
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(Map.of("message", "서버 오류가 발생했습니다."));
        }
    }
}

 

이러한 과정 때문에 생기는 코드 중복 문제를 해결하고, 일관된 상태 메시지를 제공하기 위해 Custom Exception과 ExecptionHandler를 사용할 수 있다.


구현 과정

BaseException

@Getter
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class BaseException extends RuntimeException {

    HttpStatus statusCode;
    String responseMessage;

    public BaseException(HttpStatus statusCode) {
        super();
        this.statusCode = statusCode;
    }

    public BaseException(HttpStatus statusCode, String responseMessage) {
        super(responseMessage);
        this.statusCode = statusCode;
        this.responseMessage = responseMessage;
    }

    public BaseException(ErrorStatus errorStatus) {
        super(errorStatus.getMessage());
        this.statusCode = errorStatus.getHttpStatus();
        this.responseMessage = errorStatus.getMessage();
    }

    public int getStatusCode() {
        return this.statusCode.value();
    }
}
  • 커스텀 예외 클래스들의 부모 클래스 역할을 수행
  • RuntimeException을 상속
  • HttpStatus, 에러 메시지를 받는 생성자

 

ErrorStatus

예외 상황에 맞는 상태코드와 메시지를 한곳에서 관리하는 enum이다.

@Getter
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
public enum ErrorStatus {
    

    private final HttpStatus httpStatus;
    private final String message;
    
    NOT_FOUND_USER(HttpStatus.NOT_FOUND,"사용자를 찾을 수 없습니다."),
    NOT_FOUND_SAVED_JOB(HttpStatus.NOT_FOUND, "저장 내역이 없습니다."),

	//...
    
    public int getStatusCode() {
        return this.httpStatus.value();
    }
}

 

Custom Exception

BaseException을 상속받은 구체적인 커스텀 예외 클래스이다.

각 상황에 맞는 ErrorStatus(상태코드, 에러 메시지)를 이용해 BaseExeption의 생성자를 호출해줌으로써 예외가 발생했을 때 HTTP 상태코드와 에러 메시지를 자동으로 세팅할 수 있다.

public class NotFoundException extends BaseException {

    public NotFoundException() {
        super(HttpStatus.NOT_FOUND);
    }

    public NotFoundException(String message) {
        super(HttpStatus.NOT_FOUND, message);
    }
    public NotFoundException(ErrorStatus errorStatus) {
        super(errorStatus);
    }
}

 

 

GlobalExceptionHandler

GlobalExceptionHandler 클래스는 스프링 애플리케이션 전역에서 발생하는 모든 예외를 한 곳에서 처리하는 핵심 클래스이다.

@RestControllerAdvice와 @ExceptionHandler로 모든 컨트롤러에서 발생하는 예외를 감지하고 상황에 맞게 처리할 수 있도록 해준다.

@RestControllerAdvice

  • 모든 @RestController에서 발생한 예외를 감지해 공통적으로 처리

@ExceptionHandler

  • 특정 예외 타입이 발생했을 때 실행할 처리 로직을 지정
@RestControllerAdvice
public class GlobalExceptionHandler {

    /** 커스텀 예외(BaseException) */
    @ExceptionHandler(BaseException.class)
    public ResponseEntity<ApiResponse<Void>> handleBase(BaseException ex) {
        final int code = ex.getStatusCode();
        final String msg = ex.getResponseMessage() != null
                ? ex.getResponseMessage()
                : HttpStatus.valueOf(code).getReasonPhrase();
        return ResponseEntity.status(code).body(ApiResponse.fail(code, msg));
    }

    // ...
}

 

 

의도적으로 커스텀 예외인 NotFoundException을 발생시켜보면 응답결과가 다음과 같이 상태코드, 메시지와 함께 출력되는 것을 확인할 수 있다.