| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 |
| 29 | 30 | 31 |
- HashMap
- vscode
- javascript
- java테스트
- 정규식
- Java
- 스프링부트
- IntelliJ
- 배열
- 인텔리제이
- 문자열
- input
- math
- Array
- 자바문법
- junit5
- string
- Visual Studio Code
- list
- CSS
- 테스트자동화
- js
- junit
- 단위테스트
- SpringBoot
- 자바스크립트
- html
- Eclipse
- 자바
- ArrayList
- Today
- Total
어제 오늘 내일
[Spring Boot] 실무편 - 예외 처리의 완성, 전역 예외 처리 (@RestControllerAdvice) 본문
[Spring Boot] 실무편 - 예외 처리의 완성, 전역 예외 처리 (@RestControllerAdvice)
hi.anna 2026. 2. 28. 07:33이번에는 비즈니스 로직 수행 중 발생하는 모든 예외를 한곳에서 관리하는 [전역 예외 처리] 방법을 정리해 드리겠습니다.
1. 왜 필요한가요?
개발을 하다 보면 "존재하지 않는 회원입니다", "비밀번호가 틀렸습니다"와 같이 다양한 예외 상황이 발생합니다.
이때마다 각 컨트롤러에서 try-catch를 사용하여 예외를 처리하면 코드가 중복되고 관리가 어려워집니다.
또한, 예외 처리를 하지 않아 500 Internal Server Error가 그대로 클라이언트에게 전달되면, 보안상 좋지 않을뿐더러 프론트엔드 입장에서도 원인을 파악하기 어렵습니다.
우리는 @RestControllerAdvice를 사용하여 시스템 전반에서 발생하는 모든 예외를 깔끔한 JSON 포맷으로 통일하여 응답하도록 만들 것입니다.
Step 1. 에러 코드 정의 (ErrorCode Enum)
단순히 문자열로 에러 메시지를 하드코딩하기보다, Enum을 활용하여 에러 코드와 메시지를 체계적으로 관리하는 것이 좋습니다.
- 위치:
src/main/java/com/example/board/exception/ErrorCode.java
package com.example.board.exception;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.http.HttpStatus;
@AllArgsConstructor
@Getter
public enum ErrorCode {
// 400 BAD_REQUEST: 잘못된 요청
INVALID_PASSWORD(HttpStatus.BAD_REQUEST, "비밀번호가 일치하지 않습니다."),
INVALID_INPUT_VALUE(HttpStatus.BAD_REQUEST, "입력값이 올바르지 않습니다."),
// 404 NOT_FOUND: 리소스를 찾을 수 없음
MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 유저 정보를 찾을 수 없습니다."),
POST_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 게시글을 찾을 수 없습니다."),
// 409 CONFLICT: 데이터 중복
DUPLICATE_EMAIL(HttpStatus.CONFLICT, "이미 가입된 이메일입니다."),
// 500 INTERNAL_SERVER_ERROR: 서버 에러
INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 에러가 발생했습니다.");
private final HttpStatus status;
private final String message;
}
Step 2. 커스텀 예외 클래스 생성 (CustomException)
우리가 의도적으로 발생시키는 예외를 담을 클래스입니다. RuntimeException을 상속받아 만듭니다.
- 위치:
src/main/java/com/example/board/exception/CustomException.java
package com.example.board.exception;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
public class CustomException extends RuntimeException {
private final ErrorCode errorCode;
}
Step 3. 에러 응답 포맷 정의 (ErrorResponse)
클라이언트에게 항상 동일한 JSON 형태(status, code, message)로 에러를 전달하기 위해 DTO를 만듭니다.
- 위치:
src/main/java/com/example/board/dto/ErrorResponse.java
package com.example.board.dto;
import com.example.board.exception.ErrorCode;
import lombok.Builder;
import lombok.Getter;
import org.springframework.http.ResponseEntity;
@Getter
@Builder
public class ErrorResponse {
private int status;
private String name;
private String message;
public static ResponseEntity<ErrorResponse> toResponseEntity(ErrorCode errorCode) {
return ResponseEntity
.status(errorCode.getStatus())
.body(ErrorResponse.builder()
.status(errorCode.getStatus().value())
.name(errorCode.name())
.message(errorCode.getMessage())
.build()
);
}
}
Step 4. 전역 예외 핸들러 구현 (GlobalExceptionHandler)
이제 가장 중요한 핸들러입니다. 기존에 만들었던 GlobalExceptionHandler에 CustomException 처리 로직을 추가합니다.
- 위치:
src/main/java/com/example/board/exception/GlobalExceptionHandler.java
package com.example.board.exception;
import com.example.board.dto.ErrorResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
// 1. 우리가 직접 정의한 비즈니스 예외 처리
@ExceptionHandler(CustomException.class)
protected ResponseEntity<ErrorResponse> handleCustomException(CustomException e) {
log.error("handleCustomException throw CustomException : {}", e.getErrorCode());
return ErrorResponse.toResponseEntity(e.getErrorCode());
}
// 2. 그 외 모든 예외 처리 (예상치 못한 서버 에러)
@ExceptionHandler(Exception.class)
protected ResponseEntity<ErrorResponse> handleException(Exception e) {
log.error("handleException throw Exception : {}", e.getMessage());
return ErrorResponse.toResponseEntity(ErrorCode.INTERNAL_SERVER_ERROR);
}
}Step 5. 사용 예시 (Service)
이제 서비스 로직에서 예외 상황이 발생하면 throw new CustomException(...)을 호출하기만 하면 됩니다.
- 위치:
src/main/java/com/example/board/service/MemberService.java
@Transactional
public MemberResponse getMemberInfo(String email) {
Member member = memberRepository.findByEmail(email)
.orElseThrow(() -> new CustomException(ErrorCode.MEMBER_NOT_FOUND)); // ★ 여기서 예외 발생
return MemberResponse.of(member);
}
@Transactional
public void join(MemberJoinDto dto) {
if (memberRepository.existsByEmail(dto.getEmail())) {
throw new CustomException(ErrorCode.DUPLICATE_EMAIL); // ★ 중복 회원 예외 발생
}
// ...
}
📊 동작 흐름 (Sequence Diagram)

🎉 마무리
이제 여러분의 서버는 어떤 상황에서도 당황하지 않고, 약속된 JSON 형식으로 에러 원인을 클라이언트에게 명확하게 전달할 수 있게 되었습니다.
프론트엔드 개발자는 이제 500 에러를 보고 "서버가 죽었나요?"라고 묻는 대신, 응답의 message를 보고 "아, 회원이 없구나"라고 이해하고 처리할 수 있게 됩니다.
'IT > SpringBoot' 카테고리의 다른 글
| [Spring Boot] 마법의 비밀, Auto Configuration (자동 설정) (1) | 2026.03.01 |
|---|---|
| [Spring Boot] SpringBoot Starter가 도대체 뭔가요? 의존성 지옥 탈출! (0) | 2026.03.01 |
| [Spring Boot] 필수 어노테이션 총정리 (Cheat Sheet) 📝 (0) | 2026.02.28 |
| [Spring Boot] AOP 심화 분석 - LogAspect 코드 뜯어보기 & 문법 완벽 정리 (0) | 2026.02.27 |
| [Spring Boot] 로그, 노가다 그만! AOP로 요청/응답/시간 자동 기록하기 (0) | 2026.02.27 |
