반응형
Java 커스텀 예외(Custom Exception) 설계 완벽 이해하기
실무 프로젝트를 하다 보면 어느 순간 이런 코드가 등장합니다.
throw new RuntimeException("회원이 존재하지 않습니다.");
처음에는 문제가 없어 보입니다.
하지만 프로젝트가 커질수록:
RuntimeException
IllegalArgumentException
NullPointerException
만으로는
도대체 무슨 문제가 발생한 건지
구분하기 어려워집니다.
그래서 등장하는 것이 바로:
Custom Exception (커스텀 예외)
입니다.
특히 Spring 실무에서는:
- BusinessException
- NotFoundException
- UnauthorizedException
- ValidationException
같은 예외를 직접 정의해서 사용합니다.
1. 커스텀 예외란?
커스텀 예외는:
개발자가 직접 정의한 예외 클래스
입니다.
2. 왜 필요할까?
예:
throw new RuntimeException("상품 없음");
로그를 보면
RuntimeException
만 보입니다.
그런데
throw new ProductNotFoundException();
이면
아 상품 조회 실패구나
즉시 알 수 있습니다.
3. 가장 단순한 커스텀 예외
public class ProductNotFoundException
extends RuntimeException {
}
사용
throw new ProductNotFoundException();
4. 보통 메시지 추가
실무에서는 거의 이렇게 작성
public class ProductNotFoundException
extends RuntimeException {
public ProductNotFoundException() {
super("상품이 존재하지 않습니다.");
}
}
5. 결과
throw new ProductNotFoundException();
로그
ProductNotFoundException:
상품이 존재하지 않습니다.
6. RuntimeException을 상속하는 이유
매우 중요.
현대 Spring 실무에서는 대부분
extends RuntimeException
사용.
이유
Checked Exception 지옥 방지
예
findUser()
throws UserNotFoundException
이걸 Service
Controller
전부 선언해야 함.
그래서 대부분
public class UserNotFoundException
extends RuntimeException
선택.
7. 예외 전파 구조
Repository
↓
Service
↓
Controller
↓
@ControllerAdvice
중간에서 잡지 않고
최상위까지 전파
하는 경우가 많음.
8. 실무에서 가장 흔한 구조
public class BusinessException
extends RuntimeException {
public BusinessException(String message) {
super(message);
}
}
사용
throw new BusinessException(
"재고가 부족합니다."
);
9. 그런데 이것도 한계
로그
BusinessException
만 보임.
그래서 보통 세분화
10. 실무형 구조
BusinessException
상속
ProductNotFoundException
OrderAlreadyCompletedException
StockNotEnoughException
UserNotFoundException
구조
RuntimeException
↑
BusinessException
↑
├─ ProductNotFoundException
├─ UserNotFoundException
└─ StockNotEnoughException
11. 예외 코드 관리
대규모 프로젝트는
메시지 대신 코드 사용.
예
USER_NOT_FOUND
ORDER_ALREADY_COMPLETED
12. enum 활용
public enum ErrorCode {
USER_NOT_FOUND,
PRODUCT_NOT_FOUND,
STOCK_NOT_ENOUGH
}
13. 예외 객체에 포함
public class BusinessException
extends RuntimeException {
private final ErrorCode errorCode;
public BusinessException(
ErrorCode errorCode) {
this.errorCode = errorCode;
}
}
14. 왜 코드가 필요할까?
메시지는 변경 가능.
예
회원이 존재하지 않습니다.
↓
존재하지 않는 회원입니다.
메시지가 바뀌면
프론트가 파싱 불가.
코드는 안 바뀜.
USER_NOT_FOUND
15. Spring에서 사용하는 패턴
Controller
@GetMapping("/{id}")
public User findUser(
@PathVariable Long id) {
return userService.findUser(id);
}
Service
public User findUser(Long id) {
return repository.findById(id)
.orElseThrow(
UserNotFoundException::new
);
}
16. 전역 예외 처리
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(
UserNotFoundException.class
)
public ResponseEntity<?> handle() {
return ResponseEntity
.status(404)
.build();
}
}
17. Exception Translation
실무 핵심.
예
Repository
SQLException
발생.
Service까지
SQLException
전파?
좋지 않음.
18. 변환
catch(SQLException e) {
throw new UserRepositoryException(e);
}
이것을
Exception Translation
이라고 함.
19. Spring도 사용
Spring DataAccessException
대표적.
원래
SQLException
↓
Spring
DataAccessException
변환.
20. 예외 체이닝(Exception Chaining)
매우 중요.
안 좋은 예
catch(SQLException e) {
throw new RuntimeException();
}
문제
원인 사라짐.
21. 좋은 예
catch(SQLException e) {
throw new UserRepositoryException(e);
}
22. 생성자
public UserRepositoryException(
Throwable cause) {
super(cause);
}
결과
UserRepositoryException
↓
SQLException
원인 추적 가능.
23. 실무 예외 계층 예시
RuntimeException
↑
BusinessException
↑
├─ UserException
│ ├─ UserNotFoundException
│ └─ DuplicateUserException
│
├─ OrderException
│ ├─ OrderAlreadyCompletedException
│ └─ OrderCancelException
│
└─ ProductException
├─ ProductNotFoundException
└─ StockNotEnoughException
24. 자주 하는 실수
1) RuntimeException만 사용
throw new RuntimeException();
원인 파악 어려움.
2) 메시지만으로 구분
"상품 없음"
"회원 없음"
비효율.
3) 원인 예외 제거
throw new CustomException();
cause 누락.
4) 예외 클래스 과도 생성
ProductNotFoundByIdException
ProductNotFoundByCodeException
너무 세분화.
25. 실무 추천 구조
중소 규모
BusinessException
ErrorCode
중대형 규모
BusinessException
↓
도메인별 Exception
26. 핵심 흐름 요약
예외 발생
↓
Custom Exception 생성
↓
상위 계층 전파
↓
@ControllerAdvice
↓
HTTP 응답 변환
27. 가장 중요한 핵심 한 줄
커스텀 예외의 목적은
예외를 처리하기 위한 것이 아니라
예외의 의미를 명확하게 표현하기 위한 것이다.
28. 면접 단골 질문
Q. 왜 RuntimeException 기반으로 만드나요?
계층 오염 방지
+
Spring 트랜잭션 연동
+
코드 단순화
Q. ErrorCode를 왜 사용하나요?
메시지는 변경되지만
코드는 변경되지 않기 때문
Q. 예외를 감싸는 이유는?
원인 예외 보존
+
비즈니스 의미 부여
다음 글은 "예외 로그 전략" 으로 진행하면 됩니다.
여기서부터는 단순 Java 문법이 아니라 실제 운영 장애 대응, 로그 설계, 중복 로그 방지, MDC, Trace ID 같은 실무 영역으로 들어가게 됩니다.
반응형
'language > java' 카테고리의 다른 글
| Java/Spring 실무 예외 처리 패턴 완벽 이해하기 (0) | 2026.05.29 |
|---|---|
| Java 예외 로그 전략 완벽 이해하기 (0) | 2026.05.29 |
| Java try-with-resources 원리 완벽 이해하기 (0) | 2026.05.29 |
| Java try-catch-finally 흐름 완벽 이해하기 (0) | 2026.05.29 |
| Java Checked vs Unchecked Exception 완벽 이해하기 (0) | 2026.05.28 |
댓글