티스토리 뷰

CustomException

백엔드 시스템을 개발하다 보면 정상적인 흐름에서 벗어난 다양한 예외 상황을 만나게 됩니다. 이러한 예외를 어떻게 처리하느냐에 따라 코드의 가독성, 유지보수성, 그리고 일관성이 크게 달라집니다.

이 글에서는 Custom Exception을 사용했을 때의 이점과, Spring에서 이를 어떻게 효율적으로 처리할 수 있는지를 다뤄보겠습니다.

1. CustomException 없이 예외 처리할 때

조건 분기 방식

가장 많이 접하는 형태입니다. 분기를 사용하여 예외처리를 한 코드이다. 해당 코드가 나쁜 코드라고 단언할 수 없으나 예외 상황에 대해 의미 전달력이 떨어지고 개발자마다 예외 메시지 등 일관적인 처리가 달라질 가능성이 충분하다. 그리고 예외 상황마다 반복적으로 나타나는 코드가 견디기 힘들다.

public Map<String, Object> getUser(String userId) {
    Map<String, Obejct> result = new HashMap<>();
    User user = userRepository.findById(userId);
    if (user == null) {
        result.put("code", 404);
        result.put("message", "사용자 없음");
        return result;
    }

    if (!user.isActive()) {
        result.put("code", 404);
        result.put("message", "비활성 사용자");
        return result;
    }

    result.put("code", 200);
    result.put("message", "success");
    result.put("data", user);
    return result;
}

RuntimeException 직접 사용

이 방법은 코드가 간결해지지만, 예외가 어떤 종류인지 분류하기 어렵고, 전역적으로 구분도 불가능해집니다.

if (user == null) {
    throw new RuntimeException("사용자 없음");
}

2. CustomException을 사용한 예외

예외 클래스 정의

public class UserNotFoundException extends RuntimeException {
    public UserNotFoundException(Long id) {
        super("사용자 ID " + id + "를 찾을 수 없습니다.");
    }
}

public class InactiveUserException extends RuntimeException {
    public InactiveUserException(Long id) {
        super("사용자 ID " + id + "는 비활성 상태입니다.");
    }
}

사용 예시

public User getUser(Long userId) {
    User user = userRepository.findById(userId);
    if (user == null) {
        throw new UserNotFoundException(userId);
    }

    if (!user.isActive()) {
        throw new InactiveUserException(userId);
    }

    return user;
}

CustomException을 사용하면 예외 발생 시 일관적인 메시지 반환이 가능하고 코드의 흐름상 의미 전달도 명확하게 된다.

3. Spring을 활용한 예외 처리 전략

Spring에서는 @ControllerAdvice@ExceptionHandler를 통해 예외 상황을 전역에서 일관되게 처리할 수 있습니다.

@RestControllerAdvice
public class GlobalExceptionHandler {

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

    @ExceptionHandler(InactiveUserException.class)
    public ResponseEntity<ErrorResponse> handleInactiveUser(InactiveUserException e) {
        return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                .body(new ErrorResponse("INACTIVE_USER", e.getMessage()));
    }
}

전역 예외처리는 기본적으로 모든 Controller에 적용되지만 패키지나, 개별 컨트롤러를 지정하여 처리 범위를 제한할 수 있다.

@ControllerAdvice(basePackages = "com.example.controllers")
@ControllerAdvice(assignableTypes = {UserController.class, ProductController.class})

예외 처리시 클라이언트에게 전달하는 응답값 ResponseEntity는 아래와 같은 구조를 이루고 있다.

new ResponseEntity<>(body, status);
new ResponseEntity<>(body, headers, status);
new ResponseEntity<>(status);

결론

CustomException은 예외 상황을 의미 있게 설명 할 수 있는 수단이며, Spring의 예외 처리 기능과 결합하면 일관되고 의미 전달렸이 높은 API를 설계할 수 있습니다. 실무에서 예외 처리가 반복되거나 불명확하다면, 이 구조를 도입해보는 걸 추천합니다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2026/05   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
글 보관함