커스텀 예외를 만들었을 때, 모든 예외에 cause를 전달해야 하는지 헷갈릴 수 있다. 결론부터 말하면, 외부 시스템의 예외를 감싸는 경우에만 필요하다. 내 코드에서 직접 던지는 예외에는 감쌀 원본이 없으니까.
예외가 발생하는 두 가지 패턴
패턴 1 - 조건 확인 후 직접 던지는 경우
코드 자체가 조건을 확인하고, 문제가 있으면 직접 예외를 생성하여 던진다.
Channel channel = channelRepository.findById(id)
.orElseThrow(() -> new ChannelNotFoundException(id));
여기서 발생하는 예외의 흐름은 이렇다.
findById() → Optional.empty() 반환
→ orElseThrow()가 ChannelNotFoundException을 생성하고 던짐
원본 예외가 존재하지 않는다. findById()는 예외를 던지지 않고, empty()를 반환하는 것이다. ChannelNotFoundException 자체가 처음과 끝이다.
// cause를 넘길 e가 없음
throw new ChannelNotFoundException(id); // ✅ 이것으로 충분
패턴 2 - 외부 시스템의 예외를 감싸는 경우
외부 시스템(AWS SDK, HTTP 클라이언트 등)이 먼저 예외를 던지고, 그것을 catch하여 커스텀 예외로 변환하는 경우다.
try {
s3Client.putObject(...); // ← AWS SDK가 예외를 던짐
} catch (Exception e) { // ← e는 SDK가 던진 원본 예외
throw new S3UploadException(fileName, e);
}
여기서 발생하는 예외의 흐름은 이렇다.
s3Client.putObject() → S3Exception("Access Denied") 발생
→ catch에서 잡음
→ S3UploadException을 생성하고 던짐
원본 예외(e)가 존재한다. e를 버리면 S3에서 실제로 무엇이 잘못되었는지가 사라진다.
// ❌ e를 버리면 실제 원인 소멸
throw new S3UploadException(fileName);
// ✅ e를 전달하면 Caused by로 남음
throw new S3UploadException(fileName, e);
두 패턴의 차이 한눈에 보기
| 패턴 1 - 조건 확인 | 패턴 2 - 외부 예외 감싸기 | |
|---|---|---|
| 예외의 출발점 | 내 코드가 직접 생성 | 외부 시스템이 먼저 발생 |
| 원본 예외 존재 여부 | 없다 | 있다 (catch의 e) |
cause 필요 여부 | 불필요 | 필요 |
| 디버깅 시 확인해야 할 정보 | 예외 메시지와 파라미터 | 원본 예외의 메시지와 스택트레이스 |
현재 프로젝트에서의 적용
| 예외 클래스 | 패턴 | cause 필요 여부 |
|---|---|---|
ChannelNotFoundException | 조건 확인 (findById → empty) | 불필요 |
UserNotFoundException | 조건 확인 (findById → empty) | 불필요 |
BinaryContentNotFoundException | 조건 확인 (findById → empty) | 불필요 |
BinaryContentAlreadyExistException | 조건 확인 (Files.exists()) | 불필요 |
S3UploadException | 외부 예외 감싸기 (s3Client.putObject()) | 필요 |
S3DownloadException | 외부 예외 감싸기 (s3Client.getObject()) | 필요 |
cause를 전달하면 스택트레이스는 어떻게 되나
// S3UploadException(fileName, e)를 던질 때의 로그
S3UploadException: 파일 업로드 실패 - image.png
at S3BinaryContentStorage.put(S3BinaryContentStorage.java:52)
...
Caused by: software.amazon.awssdk.services.s3.model.S3Exception: Access Denied // ← 실제 원인
at software.amazon.awssdk...
...
Caused by 아래가 실제 원인이다. 이건 RuntimeException의 기본 기능이고, super(message, cause)로 원본 예외를 전달하면 자동으로 붙는다.
요약
외부 시스템이 예외를 던진 후, 그것을 catch하여 변환하는 경우에만 cause를 전달한다. 조건을 직접 확인하고 예외를 생성하는 경우는 원본 예외가 존재하지 않으므로 불필요하다.