Contents
see List작성일 2025. 11. 30.
Java 예외 처리 전략
효과적인 예외 처리는 안정적인 애플리케이션의 핵심입니다. 체크 예외와 언체크 예외를 적절히 사용하고, 의미 있는 예외 메시지와 로깅으로 디버깅을 쉽게 만들어야 합니다.
예외 계층 구조
Throwable
├── Error (시스템 오류, 처리 불가)
│ └── OutOfMemoryError, StackOverflowError
└── Exception
├── Checked Exception (컴파일러가 체크)
│ └── IOException, SQLException
└── RuntimeException (Unchecked, 런타임 체크)
└── NullPointerException, IllegalArgumentException체크 vs 언체크 예외 선택 기준
| 체크 예외 | 언체크 예외 |
|---|---|
| 호출자가 복구 가능할 때 | 프로그래밍 오류일 때 |
| 외부 리소스 접근 실패 | 잘못된 인자, null 참조 |
| 반드시 처리해야 할 때 | 개발자 실수 |
커스텀 예외 정의
// 비즈니스 예외 기본 클래스
public class BusinessException extends RuntimeException {
private final ErrorCode errorCode;
public BusinessException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.errorCode = errorCode;
}
public BusinessException(ErrorCode errorCode, Throwable cause) {
super(errorCode.getMessage(), cause);
this.errorCode = errorCode;
}
public ErrorCode getErrorCode() {
return errorCode;
}
}
// 에러 코드 enum
public enum ErrorCode {
USER_NOT_FOUND("U001", "사용자를 찾을 수 없습니다"),
INVALID_PASSWORD("U002", "비밀번호가 일치하지 않습니다"),
DUPLICATE_EMAIL("U003", "이미 등록된 이메일입니다");
private final String code;
private final String message;
// constructor, getters
}
// 구체적인 예외
public class UserNotFoundException extends BusinessException {
public UserNotFoundException() {
super(ErrorCode.USER_NOT_FOUND);
}
}예외 처리 Best Practices
// 1. 구체적인 예외를 먼저 catch
try {
// 작업
} catch (FileNotFoundException e) {
// 파일 없음 처리
} catch (IOException e) {
// 기타 I/O 오류
}
// 2. 예외 체이닝 (원인 보존)
try {
parseData(input);
} catch (ParseException e) {
throw new BusinessException(ErrorCode.PARSE_ERROR, e);
}
// 3. try-with-resources 사용
try (InputStream is = new FileInputStream(file);
BufferedReader reader = new BufferedReader(
new InputStreamReader(is))) {
return reader.readLine();
}
// 4. 불필요한 catch 금지
// BAD
try {
doSomething();
} catch (Exception e) {
throw e; // 의미없는 재던지기
}
// 5. 로깅 후 던지기 (중복 로깅 주의)
try {
process();
} catch (Exception e) {
log.error("처리 실패: {}", e.getMessage());
throw new ProcessException(e);
}Spring 글로벌 예외 처리
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger log =
LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ErrorResponse> handleBusiness(
BusinessException e) {
log.warn("비즈니스 예외: {}", e.getMessage());
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(new ErrorResponse(e.getErrorCode()));
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleUnexpected(
Exception e) {
log.error("예상치 못한 오류", e);
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ErrorResponse.of("서버 오류가 발생했습니다"));
}
}
// 에러 응답 DTO
@Getter
public class ErrorResponse {
private String code;
private String message;
private LocalDateTime timestamp;
public ErrorResponse(ErrorCode errorCode) {
this.code = errorCode.getCode();
this.message = errorCode.getMessage();
this.timestamp = LocalDateTime.now();
}
}Optional로 null 예외 방지
// Optional 활용
public User findUser(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException());
}
// null 체크 대신 Optional
Optional.ofNullable(value)
.map(String::trim)
.filter(s -> !s.isEmpty())
.orElse("default");예외 처리 안티패턴
- 빈 catch 블록 (예외 삼키기)
- Exception으로 모든 것 catch
- 예외를 흐름 제어에 사용
- 필요 이상으로 넓은 범위의 try
java
| No | 작성일 | Title |
|---|---|---|
| 2198 | 2026. 02. 11. | Java Record와 Sealed Class 실전 디자인 패턴 |
| 2197 | 2026. 02. 11. | Java 23 신기능: Structured Concurrency와 Scoped Values |
| 2141 | 2026. 02. 11. | Java 빌드 도구 비교: Gradle vs Maven 2025 |
| 2140 | 2026. 02. 11. | JUnit 5와 Mockito를 활용한 효과적인 단위 테스트 |
| 2139 | 2026. 02. 11. | Java에서의 함수형 프로그래밍 패턴 |
| 2138 | 2026. 02. 11. | Project Loom, Panama, Valhalla: Java의 미래 |
| 2137 | 2026. 02. 11. | Java 메모리 관리와 GC 튜닝 실전 가이드 |
| 2136 | 2026. 02. 11. | GraalVM과 Java: 네이티브 컴파일의 실전 적용 |
| 2135 | 2026. 02. 11. | Java Stream API 고급 활용과 성능 최적화 |
| 2134 | 2026. 02. 11. | Record와 Sealed Class로 견고한 도메인 모델링 |