이전 글에서는 단순히 GlobalExceptionHandler로 기존에 존재하던 표준 예외들을 처리하는 로직을 만들었는데, 직접 예외 클래스를 정리하고 그에 맞는 예외처리 로직도 구현할 수 있다. 이것을 커스텀 예외처리라고 한다.
표준 예외만 사용하는 경우에는
RuntimeException,IllegalArgumentException만 사용하면 예외의 의도를 코드만으로 파악하기 어렵고, HTTP 상태 코드나 메세지 등을 세분화하거나 통합 관리하기 어렵다는 단점이 있다. 이러한 단점들을 보완하기 위해 커스텀 예외가 필요하다.
@Getter
@AllArgsConstructor
public enum ErrorCode {
INVALID_PASSWORD(401, "비밀번호가 맞지않습니다"),
INTERNAL_ERROR(500, "서버 내부 오류가 발생했습니다-ErrorCode Enum"),
USER_NOT_FOUND(404, "해당 유저를 찾을 수 없습니다"),
DUPLICATE_USERID(1003, "이미 존재하는 아이디입니다");
private final int code;
private final String message;
}
@Getter
@AllArgsConstructor
public class ErrorResponse {
private int code; //상태코드
private String message; //커스텀 에러 메시지(사용자용 메시지 ex.로그인 실패)
private String detail; //실제 에러 메세지(디버깅용 메시지 ex.Exception.getMessage())
public static ErrorResponse of(ErrorCode errorCode) {
return new ErrorResponse(errorCode.getCode(), errorCode.getMessage(), null);
}
public static ErrorResponse of(ErrorCode errorCode, String detail) {
return new ErrorResponse(errorCode.getCode(), errorCode.getMessage(), detail);
}
}
ErrorResponse에는 정적 팩토리 메서드도 만들어줬는데, 상세 메시지 없이 간단한 오류 응답을 생성하는 메서드와 디버깅 정보까지 포함하여 응답을 생성하는 메서드 총 2가지를 만들었다. @ExceptionHandler(RuntimeException.class)
public ResponseEntity<ErrorResponse> handleRuntimeException(RuntimeException e){
return ResponseEntity
.status(HttpStatus.UNAUTHORIZED)
.body(ErrorResponse.of(ErrorCode.INTERNAL_ERROR, e.getMessage()));
}
@RestController
@RequestMapping("/exception")
public class TestController {
@GetMapping
public ResponseEntity<ErrorResponse> test() {
throw new RuntimeException("글로벌 예외처리 테스트중~");
}
}

message : ErrorCode에서 예외 생성 시 작성한 메시지detail : Controller에서 throw new로 예외를 넘길때 작성한 메시지이런식으로 커스텀 예외처리를 하게되면
INVALID_PASSWORD,DUPLICATE_USERID등 도메인에 맞는 명확한 의미 전달이 가능하고, 각 예외에 맞는 HTTP 상태코드, 메시지, 응답 구조를 제어할 수 있다는 이점이 있다. 또한, enum타입의 ErrorCode와 함께 사용하면 여러 응답을 일관되게 구성할 수 있으며 예외를 throw하는 코드와 예외를 처리하는 코드를 분리할 수 있다.