Java/Spring 실무 예외 처리 패턴 완벽 이해하기
지금까지 배운:
- Exception 구조
- Checked vs Unchecked
- Custom Exception
- 예외 로그 전략
- try-catch-finally
를 실제 Spring 프로젝트에서는 어떻게 사용할까요?
많은 초급 개발자의 코드는 보통 이렇게 시작합니다.
try {
...
} catch(Exception e) {
e.printStackTrace();
return "ERROR";
}
하지만 실무에서는 거의 이런 방식으로 처리하지 않습니다.
대부분 다음 구조를 사용합니다.
Repository
↓
Service
↓
Controller
↓
@ControllerAdvice
예외는 위로 던지고
로그는 한 번만 남기고
응답은 전역에서 통일합니다.
1. 가장 안 좋은 예외 처리
예:
public User findUser(Long id) {
try {
return repository.findById(id);
} catch(Exception e) {
log.error("조회 실패");
return null;
}
}
2. 문제점
예외가 사라짐
실패했는데 성공처럼 보임
원인 추적 불가
StackTrace 유실
Null 지옥 시작
user.getName();
↓
NullPointerException
발생.
3. 실무 기본 원칙
매우 중요.
예외는 가능한 한 빨리 잡지 않는다.
4. 왜?
실패 원인을 가장 잘 아는 곳은
보통 최상위 계층.
5. 권장 구조
Repository
↓
Service
↓
Controller
↓
ControllerAdvice
6. Repository
public User findById(Long id) {
...
}
예외 발생
↓
그냥 던짐.
7. Service
public User findUser(Long id) {
return repository.findById(id)
.orElseThrow(
UserNotFoundException::new
);
}
8. Controller
@GetMapping("/{id}")
public User findUser(
@PathVariable Long id) {
return userService.findUser(id);
}
9. 여기까지 catch 없음
매우 중요.
예외를 잡지 않는다.
10. 최종 처리
@RestControllerAdvice
public class GlobalExceptionHandler {
}
11. 왜 좋을까?
예외 처리 위치 통일.
12. 대표 패턴
비즈니스 예외
public class UserNotFoundException
extends RuntimeException {
}
Service
throw new UserNotFoundException();
13. ControllerAdvice
@ExceptionHandler(
UserNotFoundException.class
)
public ResponseEntity<?> handle() {
return ResponseEntity
.status(404)
.body("회원 없음");
}
14. 결과
예외 발생
↓
자동 전파
↓
404 반환
15. ErrorCode 패턴
실무 최다 사용.
Enum
public enum ErrorCode {
USER_NOT_FOUND,
DUPLICATE_USER,
INVALID_REQUEST
}
16. Custom Exception
public class BusinessException
extends RuntimeException {
private final ErrorCode errorCode;
public BusinessException(
ErrorCode errorCode) {
this.errorCode = errorCode;
}
}
17. 사용
throw new BusinessException(
ErrorCode.USER_NOT_FOUND
);
18. ControllerAdvice
@ExceptionHandler(
BusinessException.class
)
public ResponseEntity<?> handle(
BusinessException e) {
return ResponseEntity
.badRequest()
.body(e.getErrorCode());
}
19. 장점
프론트는
USER_NOT_FOUND
만 보고 처리 가능.
20. Exception Translation
실무 핵심.
Repository
SQLException
발생.
Service까지 그대로 전달?
좋지 않음.
21. 변환
catch(SQLException e) {
throw new UserRepositoryException(e);
}
22. 이유
하위 기술 의존 제거.
좋은 구조
Service는
SQLException
존재 자체를 모름
23. Spring도 사용
대표:
Spring DataAccessException
원래
SQLException
↓
Spring
DataAccessException
변환.
24. 로그는 어디서 남길까?
매우 중요.
안 좋은 예
Repository
log.error()
Service
log.error()
Controller
log.error()
25. 결과
같은 예외
3번 출력
26. 좋은 패턴
최상위에서 1회
ControllerAdvice
@ExceptionHandler(Exception.class)
public ResponseEntity<?> handle(
Exception e) {
log.error("시스템 오류", e);
...
}
27. 예외는 변환만 하고 로그는 안 남김
예:
catch(SQLException e) {
throw new UserRepositoryException(e);
}
28. 왜?
최상위에서 이미 로그 남길 예정.
29. Validation 예외 처리
실무 매우 많음.
예
@NotBlank
private String name;
실패
MethodArgumentNotValidException
발생.
30. 전역 처리
@ExceptionHandler(
MethodArgumentNotValidException.class
)
31. 응답 예시
{
"code":"INVALID_REQUEST",
"message":"이름은 필수입니다."
}
32. 표준 에러 응답 객체
실무 필수.
public record ErrorResponse(
String code,
String message
) {}
33. 응답 통일
{
"code":"USER_NOT_FOUND",
"message":"회원이 존재하지 않습니다."
}
{
"code":"INVALID_REQUEST",
"message":"입력값 오류"
}
항상 동일 구조.
34. 트랜잭션과 연결
매우 중요.
@Transactional
RuntimeException 발생
↓
자동 Rollback
35. 예시
@Transactional
public void order() {
saveOrder();
throw new OrderException();
}
결과
전체 롤백
36. 예외를 삼키면?
try {
} catch(Exception e) {
}
결과
Rollback 안 됨
37. 실무 최악 패턴
catch(Exception e) {
return false;
}
왜 위험할까?
실패를 숨김.
38. 실무 추천 구조
Controller
↓
Service
↓
Repository
예외 발생
↓
전파
↓
ControllerAdvice
↓
로그
↓
응답
39. 대규모 프로젝트 구조
RuntimeException
↑
BusinessException
↑
├─ UserException
├─ ProductException
├─ OrderException
ErrorCode
USER_NOT_FOUND
USER_DUPLICATED
PRODUCT_NOT_FOUND
ORDER_ALREADY_COMPLETED
40. 실무에서 자주 하는 실수
1)
catch(Exception e){}
예외 삼킴.
2)
모든 계층에서 log.error()
중복 로그.
3)
RuntimeException 대신
throws Exception
남발.
4)
에러 응답 포맷 제각각.
5)
예외 메시지로 분기.
if(message.contains("회원"))
절대 금지.
41. 실무 Spring 예외 처리 최종 구조
예외 발생
↓
Custom Exception
↓
전파
↓
@ControllerAdvice
↓
ErrorResponse 생성
↓
로그 1회 기록
↓
HTTP 응답 반환
42. 가장 중요한 핵심 한 줄
실무 예외 처리의 목표는
예외를 막는 것이 아니라
예외를 일관되게 전파하고,
한 곳에서 기록하고,
한 형태로 응답하는 것이다.
43. 면접 단골 질문
Q. Service에서 예외를 catch 해야 하나요?
대부분 No
전파 후 ControllerAdvice 처리
Q. 로그는 어디서 남기나요?
최상위 예외 처리 지점
Q. ErrorCode를 사용하는 이유는?
메시지 변경과 무관하게
클라이언트가 안정적으로 처리 가능
다음 글은 Spring @ExceptionHandler 입니다.
여기서부터는 지금까지 설명한 실무 예외 처리 패턴이 실제 Spring MVC 내부에서 어떻게 동작하는지 들어가게 됩니다. DispatcherServlet → ExceptionResolver → @ExceptionHandler → @ControllerAdvice 흐름까지 이해하면 Spring 예외 처리 구조가 완성됩니다.
'language > java' 카테고리의 다른 글
| Spring @ControllerAdvice / @RestControllerAdvice 완벽 이해하기 (0) | 2026.05.29 |
|---|---|
| Spring @ExceptionHandler 완벽 이해하기 (0) | 2026.05.29 |
| Java 예외 로그 전략 완벽 이해하기 (0) | 2026.05.29 |
| Java 커스텀 예외(Custom Exception) 설계 완벽 이해하기 (1) | 2026.05.29 |
| Java try-with-resources 원리 완벽 이해하기 (0) | 2026.05.29 |
댓글