| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 |
- javascript
- Java
- 테스트자동화
- vscode
- HashMap
- 단위테스트
- 스프링부트
- input
- 자바
- 자바스크립트
- 정규식
- 배열
- junit5
- html
- Visual Studio Code
- ArrayList
- math
- Eclipse
- SpringBoot
- java테스트
- list
- 문자열
- Array
- 자바문법
- 인텔리제이
- IntelliJ
- junit
- string
- CSS
- js
- Today
- Total
어제 오늘 내일
[Spring Boot] 예외 처리의 정석: try-catch 지옥에서 벗어나기 (@ExceptionHandler & @ControllerAdvice) 본문
[Spring Boot] 예외 처리의 정석: try-catch 지옥에서 벗어나기 (@ExceptionHandler & @ControllerAdvice)
hi.anna 2026. 3. 11. 00:20
개발하다 보면 비즈니스 로직보다 예외 처리 코드가 더 길어지는 주객전도 현상을 자주 겪게 됩니다.
try {
// 로직 수행...
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest().build();
} catch (Exception e) {
return ResponseEntity.internalServerError().build();
}
모든 컨트롤러마다 이런 코드를 복사-붙여넣기 하고 계신가요? 오늘은 Spring Boot가 제공하는 강력한 예외 처리 메커니즘으로 코드를 획기적으로 줄이는 방법을 소개합니다.
1. 컨트롤러 안에서 해결하기: @ExceptionHandler
특정 컨트롤러 안에서 발생하는 에러만 잡고 싶을 때 사용합니다.
@RestController
public class MemberController {
@GetMapping("/members/{id}")
public Member getMember(@PathVariable Long id) {
throw new IllegalArgumentException("멤버가 없습니다!");
}
// 이 컨트롤러에서 IllegalArgumentException이 발생하면 무조건 이 메서드가 실행됨!
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<String> handleArgumentException(IllegalArgumentException e) {
return ResponseEntity.badRequest().body(e.getMessage());
}
}
- 장점: 직관적입니다.
- 단점: 다른 컨트롤러에서도 똑같은 에러가 나면, 거기서도 또 작성해야 합니다. (중복 발생!)
2. 모든 곳의 에러를 한곳에서! @RestControllerAdvice ★강력 추천★
이 어노테이션은 애플리케이션 전역(Global)에서 발생하는 예외를 감지합니다. 즉, 예외 처리 로직을 비즈니스 로직에서 완전히 분리해 낼 수 있습니다.
① 전역 예외 처리 클래스 만들기
보통 GlobalExceptionHandler라는 이름으로 클래스를 하나 만듭니다.
@RestControllerAdvice // 모든 컨트롤러의 예외를 여기서 잡겠다!
public class GlobalExceptionHandler {
// 1. 잘못된 요청(400) 잡기
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ErrorResponse> handleBadRequest(IllegalArgumentException e) {
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(new ErrorResponse("BAD_REQUEST", e.getMessage()));
}
// 2. 알 수 없는 서버 에러(500) 잡기
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleServerError(Exception e) {
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new ErrorResponse("SERVER_ERROR", "서버 내부 오류가 발생했습니다."));
}
}
이제 컨트롤러에서는 마음 놓고 에러를 throw 하기만 하면 됩니다. 뒤처리는 이 녀석이 다 알아서 하니까요!
3. 클라이언트에게 예쁜 에러 내려주기 (DTO 활용)
그냥 문자열만 틱 던져주면 프론트엔드 개발자가 싫어합니다. JSON 형식으로 깔끔하게 약속된 규격을 내려주는 것이 좋습니다.
// 에러 응답용 DTO 클래스
@Getter
@AllArgsConstructor
public class ErrorResponse {
private String errorCode;
private String message;
}
이렇게 하면 에러가 발생했을 때 클라이언트는 항상 이런 깔끔한 JSON을 받게 됩니다.
{
"errorCode": "BAD_REQUEST",
"message": "해당 ID의 회원을 찾을 수 없습니다."
}
4. 실무 꿀팁: 커스텀 예외(Custom Exception) 만들기
자바가 기본으로 제공하는 RuntimeException만 쓰면 어떤 상황인지 명확하지 않습니다. 우리 프로젝트만의 언어로 에러를 정의해 보세요.
// 1. 커스텀 예외 정의
public class MemberNotFoundException extends RuntimeException {
public MemberNotFoundException(Long id) {
super(id + "번 회원을 찾을 수 없습니다.");
}
}
// 2. 비즈니스 로직에서 사용
if (member == null) {
throw new MemberNotFoundException(id); // 코드의 가독성이 확 올라갑니다!
}
// 3. 핸들러에서 처리
@ExceptionHandler(MemberNotFoundException.class)
public ResponseEntity<ErrorResponse> handleMemberNotFound(...) { ... }
마치며
오늘의 결론입니다.
try-catch는 이제 그만! 비즈니스 로직에만 집중하세요.@RestControllerAdvice를 사용해 에러 처리를 한곳(중앙)으로 모으세요.ErrorResponse객체를 만들어 클라이언트에게 통일된 에러 형식을 제공하세요.
이렇게 하면 코드가 놀랍도록 깔끔해지고, 유지보수하기도 훨씬 쉬워집니다. 이것이 바로 "우아한 예외 처리"입니다.
다음 포스팅에서는 Spring Boot의 유효성 검사 끝판왕: @Valid와 @Validated로 입력값 검증하기에 대해 알아보겠습니다.
도움이 되셨다면 좋아요와 댓글 부탁드립니다! 😊
'IT > SpringBoot' 카테고리의 다른 글
| [Spring Boot] 제발 System.out.println 쓰지 마세요! (feat. SLF4J & Logback) (0) | 2026.03.10 |
|---|---|
| [Spring Boot] "로컬에선 H2, 실무에선 MySQL?" 환경별 설정 관리의 정석 (Profile & YAML) (0) | 2026.03.10 |
| [Spring Boot] 마법이 아니라 과학입니다! 자동 설정(Auto Configuration)의 비밀 (0) | 2026.03.09 |
| [Spring Boot] 빈(Bean)의 탄생과 죽음: 생명주기(Lifecycle)와 스코프(Scope) 완벽 정리 (0) | 2026.03.09 |
| [Spring Boot] "객체 관리는 스프링에게 맡기세요!" 의존성 주입(DI) 완벽 가이드 (0) | 2026.03.08 |
