
개발을 하면서 발생할 수 있는 Exception 을 모두 처리하는건 쉬운 일이 아닙니다. GlobalExceptionHandler 를 쓴다 해도 Exception 이 발생할 때 마다 @ExceptionHandler를 추가하면 그 양도 많아져 관리가 힘들게 됩니다. 하나의 Exception 으로 여러 Exception 관련 처리를 하면서 원하는 메시지와 코드를 클라이언트에게 전달할 수 있도록 Custom Exception 을 생성해 활용하는 방법을 알아보겠습니다.
명칭은 ServiceException 으로 하고 RuntimeException 을 상속 받는 class 를 생성합니다.
common.exception.custom.ServiceException
public class ServiceException extends RuntimeException {
}
이전에 작성했던 Enum type의 ErrorCode 를 클래스 변수로 선언하고 접근할 수 있는 getter method 도 추가 합니다. 그리고 message 나 Throwable 을 선택적으로 받을 수 있도록 생성자를 overload 합니다.
public class ServiceException extends RuntimeException {
private final ErrorCode errorCode;
public ServiceException(ErrorCode errorCode) {
super(errorCode.messageCode());
this.errorCode = errorCode;
}
public ServiceException(ErrorCode errorCode, String message) {
super(message);
this.errorCode = errorCode;
}
public ServiceException(ErrorCode errorCode, Throwable throwable) {
super(throwable.getMessage(), throwable);
this.errorCode = errorCode;
}
public ErrorCode errorCode() {
return this.errorCode;
}
}
이제 ServiceException 에 대한 공통 처리를 위해 GlobalExceptionHandler 에 추가합니다.
common.exception.GlobalExceptionHandler
@ExceptionHandler(value = {ServiceException.class})
public ResponseEntity<ErrorResponse> handleServiceException(ServiceException e)
return generateErrorResponse(e.errorCode(), e);
}
새로 발생하는 Exception 을 처리해야 한다면 ErrorCode에 추가하여 ServiceException 내부 코드로 전달하면 하나의 ServiceException 으로 여러 Exception 관련 처리를 할 수 있습니다.
테스트를 위해 createMember method 에 memberName 은 한글/영문만 가능한 조건문을 추가 합니다. 그리고 조건에 해당하는 경우 ServiceException 을 throw 합니다.
@Override
public MemberCreateResponse createMember(MemberCreateRequest parameter) {
if (memberRepository.existsById(parameter.memberId())) {
throw new EntityExistsException("이미 존재하는 ID 입니다. -> "
+ parameter.memberId());
}
if (StringUtils.isEmpty(parameter.memberName())
|| !Pattern.compile("^[가-힣a-zA-Z]+$").matcher(
parameter.memberName()).matches()) {
throw new ServiceException(
ErrorCode.INVALID_PARAMETER, "이름은 한글/영문만 가능합니다.");
}
Member newMember = Member.builder()
.memberId(parameter.memberId())
.password(parameter.password())
.memberName(parameter.memberName())
.useYn(parameter.useYn())
.build();
memberRepository.save(newMember);
return MemberCreateResponse.builder()
.memberId(newMember.getMemberId())
.memberName(newMember.getMemberName())
.useYn(newMember.getUseYn())
.createDate(newMember.getCreateDate())
.updateDate(newMember.getUpdateDate())
.build();
}
한글 이외의 데이터를 전달하여 throw 한 ServiceException 의 정보가 출력되는지 확인합니다.

console
com.geonlee.api.common.exception.custom.ServiceException: 이름은 한글/영문만 가능합니다.
이하 생략...
응답 구조체와 로그가 구현한데로 출력되었습니다.
다국어 처리를 해야하는 경우 ErrorCode 와 message.properties에 추가하고 ErrorCode 만 전달하게 수정하면 됩니다.
checked Exception 은 Exception 을 상속받으면서 Runtime Exception 하위 클래스가 아닌 Exception 을 말합니다. 예외 처리(try ~ catch or throws) 를 하지 않으면 컴파일 오류가 발생하여 반드시 예외처리를 해줘야 합니다.
보통 파일이나 네트워크와 같이 외부환경 요인과 관련된 예외로 IOException, SQLException 등 이 있습니다.
Unchecked Exception 은 RuntimeException 을 상속받은 클래스를 말합니다. 말 그대로 runtime 중에 발생할 수 있는 예외로, 예외처리를 강제하진 않습니다.
프로그래밍에서 발생하는 논리적 오류와 관련된 예외로 NullPointerException, IllegalArgumentException 등이 있습니다.
ServiceException 에서 RuntimeException 을 상속받은 이유는 예외 처리를 강제화 하지 않아 코드의 간결성과 가독성을 높이기 위함입니다.