예외란 프로그램 실행 중에 발생하는 비정상적인 상황을 의미하며, 정상적인 흐름을 방해하는 사건이다. 개발자가 예측하고 처리 할 수 있는 문제 상황으로, 예외와 에러와는 다르다!
에러는 JVM 자체 문제등 개발자가 처리 불가능한 문제를 의미!
프로그램 실행 중 발생할 수 있는 예외적인 상황을 처리하는 방식을 의미하며, 에외를 적절히 처리하지 않으면 프로그램이 비정상적으로 종료되거나 보안 취약점, 데이터 손실, 이상 동작 등을 유발할 수 있다.
자바 예외 시스템에서 예외의 종류는
Checked와UnChecked두가지로 나눌 수 있다.Throwable ├── Error // 시스템 오류 (개발자 처리 불가) └── Exception // 애플리케이션 예외 (개발자가 처리 가능) ├── Checked Exception // 반드시 처리 필요 (try-catch or throws) └── Unchecked Exception (RuntimeException) // 선택적 처리
컴파일 시점에 반드시 처리해야하는 예외로, try-catch나 throws로 반드시 예외처리를 해야한다. 예외처리를 하지 않으면 컴파일 에러가 발생하고 아예 실행이 되지 않는다.
대표적인 예외로는IOException,SQLException,ParseException,ClassNotFoundException등이 있다.
런타임(실행) 중 발생하며 컴파일러가 신경 쓰지 않는 예외로, 해당 요청만 실패로 처리하고 다음 요청을 진행하며 예외처리를 하지 않아도 서버가 죽지 않는다.
대표적인 예외로는NullPointerException,IllegalArgumentException,IndexOutOfBoundsException,ArithmeticException등이 있다.
예외처리에는 여러 방법이 있지만
GlobalExceptionHandler를 만들어 글로벌 예외처리를 하는것이 가장 보편적인 방법이다.
GlobalExceptionHandler를 사용해 예외처리를 해보자.
@Slf4j
@RestControllerAdvice
//스프링에서 모든 컨트롤러의 예외를 한곳에서 처리하기 위한 어노테이션
//글로벌 예외처리기가 없으면 우리가 이전에 보던 장황한 에러코드가 모두 보임
public class GlobalExceptionHandler {
//컨트롤러에서 RuntimeException 에러가 발생했을때 이 메서드가 대신 처리하도록 매핑
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<ErrorResponse> handleRuntimeException(RuntimeException e){
log.warn("런타임 예외 처리: {}", e.getMessage());
ErrorResponse errorResponse = new ErrorResponse(400, "에러 전달 메시지", e.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
}
}
@ControllerAdvice + @ResponseBody의 조합으로, 반환값은 JSON 형태로 자동 직렬화되어 응답한다. @Getter
@AllArgsConstructor
public class ErrorResponse {
private int code; //상태코드
private String message; //커스텀 에러 메시지
private String detail; //실제 에러 메세지
}

2025-06-27T10:11:10.135+09:00 WARN 5620 --- [backendProject] [nio-8080-exec-1] o.e.b.exception.GlobalExceptionHandler : 런타임 예외 처리: 해당 유저를 찾을 수 없습니다.
2025-06-27T10:11:10.144+09:00 INFO 5620 --- [backendProject] [ restartedMain] o.e.b.BackendProjectApplication : Started BackendProjectApplication in 7.304 seconds (process running for 7.941)
2025-06-27T10:11:10.164+09:00 WARN 5620 --- [backendProject] [nio-8080-exec-1] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [java.lang.RuntimeException: 해당 유저를 찾을 수 없습니다.]
2025-06-27T10:11:10.135+09:00 WARN 5620 --- [backendProject] [nio-8080-exec-1] o.e.b.exception.GlobalExceptionHandler : 런타임 예외 처리: 해당 유저를 찾을 수 없습니다.
GlobalExceptionHandler클래스를 이용해서 예외처리한 로그로그레벨 : WARN메시지 : 런타임 예외 처리 : 해당 유저를 찾을 수 없습니다2025-06-27T10:11:10.144+09:00 INFO 5620 --- [backendProject] [ restartedMain] o.e.b.BackendProjectApplication : Started BackendProjectApplication in 7.304 seconds (process running for 7.941)
2025-06-27T10:11:10.164+09:00 WARN 5620 --- [backendProject] [nio-8080-exec-1] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [java.lang.RuntimeException: 해당 유저를 찾을 수 없습니다.]
ExceptionHandlerExceptionResolver가 예외를 성공적으로 처리했다는 의미의 로그Resolved : 예외가 더이상 전파되지 않고 여기서 처리되었다는 뜻발생한 예외 : RuntimeExceptionthrow new RuntimeException("해당 유저를 찾을 수 없습니다."); 또는 .orElseThrow(()->new RuntimeException("해당 유저를 찾을 수 없습니다.")); 발생@RestControllerAdvice로 정의된 GlobalExceptionHandler에서 이 RuntimeException을 감지하고 처리ErrorResponse 형태로 응답 반환ExceptionHandlerExceptionResolver)가 이 처리를 마무리하고 로그 남김
java.lang.RuntimeException: 해당 유저를 찾을 수 없습니다.
at org.example.backendproject.Auth.service.AuthService.lambda$login$0(AuthService.java:71) ~[main/:na]
at java.base/java.util.Optional.orElseThrow(Optional.java:403) ~[na:na]
at org.example.backendproject.Auth.service.AuthService.login(AuthService.java:71) ~[main/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:569) ~[na:na]
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) ~[spring-aop-6.2.7.jar:6.2.7]
...
📍왜 이런 장황한 스택레이스가 뜨는가?
글로벌 예외 처리를 안 하면, Spring MVC는 아래와 같이 동작한다.
1.RuntimeException이 던져짐 (예:Optional.orElseThrow(...))
2.@ExceptionHandler가 없으면 → Spring이 기본 처리 수행
3.DispatcherServlet이 예외를 캐치하고 콘솔에 스택트레이스를 전부 출력
4. 기본적으로 HTML 에러 페이지 (Whitelabel Error Page) 또는 그냥 500 에러 응답만 반환