Skip to content

[Feat] GlobalExceptionHandler 예외 타입 추가#189

Merged
noeyoseel merged 2 commits into
mainfrom
feat/186-global-exception-handler
Jun 23, 2026
Merged

[Feat] GlobalExceptionHandler 예외 타입 추가#189
noeyoseel merged 2 commits into
mainfrom
feat/186-global-exception-handler

Conversation

@noeyoseel

@noeyoseel noeyoseel commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator

User description

🔎 What

  • DataIntegrityViolationException → 409 Conflict
  • AccessDeniedException → 403 Forbidden
  • NoResourceFoundException → 404 Not Found
  • MissingRequestHeaderException → 400 Bad Request
  • MethodArgumentTypeMismatchException → 400 Bad Request

🔗 Issue

✅ 체크리스트

  • 브랜치 base가 적절한가요?
  • 제목이 이슈 제목과 동일한가요?
  • 최소 1명의 리뷰를 받았나요?

PR Type

Enhancement


Description

핵심 문제: GlobalExceptionHandler가 처리하는 예외 타입을 확장하여, 다양한 오류 상황에 대해 더 정확한 HTTP 상태 코드와 메시지를 반환하도록 개선합니다. [Issue #186]

주요 변경 사항:

  • DataIntegrityViolationException 발생 시 409 Conflict 응답을 반환하도록 처리합니다.

  • AccessDeniedException 발생 시 403 Forbidden 응답을 반환하도록 처리합니다.

  • NoResourceFoundException 발생 시 404 Not Found 응답을 반환하도록 처리합니다.

  • MissingRequestHeaderException 발생 시 400 Bad Request 응답을 반환하도록 처리합니다.

  • MethodArgumentTypeMismatchException 발생 시 400 Bad Request 응답을 반환하도록 처리합니다.

  • 4xx 계열 예외 발생 시 로그 레벨을 WARN으로 설정하여 불필요한 ERROR 로그를 줄였습니다.

주요 변경 파일:
이 PR의 핵심 비즈니스 로직은 src/main/java/com/Rootin/global/exception/GlobalExceptionHandler.java 파일에 집중되어 있습니다. 이 파일에서 새로운 예외 타입에 대한 핸들러 메서드가 추가되었습니다.

백엔드 API 변경 사항:

  • 기존에는 위 예외들이 발생했을 때 일괄적으로 500 Internal Server Error를 반환했지만, 이제는 각 예외 상황에 맞는 HTTP 상태 코드(400, 403, 404, 409)를 반환합니다.

  • 응답 형식은 ApiResponse<Void>로 동일하며, bodymessage 필드에 각 예외에 대한 구체적인 오류 메시지가 포함됩니다.

    • MissingRequestHeaderException: "필수 헤더가 누락되었습니다: [헤더 이름]"
    • DataIntegrityViolationException: "이미 처리된 요청이거나 데이터 제약 조건에 위배됩니다."
    • 그 외 예외: ErrorCode에 정의된 메시지 또는 "잘못된 입력입니다."

데이터베이스 스키마 변경:

  • 이 PR에는 데이터베이스 스키마 변경, 인덱스 추가 또는 마이그레이션 스크립트가 포함되어 있지 않습니다.

테스트 시나리오:
리뷰어나 QA 팀은 다음 시나리오를 통해 변경 사항을 검증할 수 있습니다.

  • 409 Conflict (DataIntegrityViolationException):

    • 고유 제약 조건이 있는 필드(예: 사용자 ID, 이메일)에 이미 존재하는 값으로 데이터를 삽입/업데이트를 시도하여 DB 제약 위반을 발생시킵니다.
    • 예시: 동일한 이메일로 회원가입을 두 번 시도합니다.
    • 예상 결과: HTTP 409 Conflict 응답과 "이미 처리된 요청이거나 데이터 제약 조건에 위배됩니다." 메시지를 받습니다.
  • 403 Forbidden (AccessDeniedException):

    • 권한이 없는 리소스에 접근을 시도합니다. (예: 다른 사용자의 개인 정보 조회, 관리자 권한이 필요한 기능 접근)
    • 예상 결과: HTTP 403 Forbidden 응답과 ErrorCode.FORBIDDEN 메시지를 받습니다.
  • 404 Not Found (NoResourceFoundException):

    • 존재하지 않는 API 엔드포인트로 요청을 보냅니다.
    • 예상 결과: HTTP 404 Not Found 응답과 ErrorCode.NOT_FOUND 메시지를 받습니다.
  • 400 Bad Request (MissingRequestHeaderException):

    • @RequestHeader로 필수 지정된 헤더(예: Authorization)를 누락한 채 요청을 보냅니다.
    • 예상 결과: HTTP 400 Bad Request 응답과 "필수 헤더가 누락되었습니다: [누락된 헤더 이름]" 메시지를 받습니다.
  • 400 Bad Request (MethodArgumentTypeMismatchException):

    • API 파라미터의 타입이 맞지 않는 값을 전달합니다. (예: Long 타입이 필요한 곳에 문자열 전달)
    • 예상 결과: HTTP 400 Bad Request 응답과 ErrorCode.INVALID_INPUT 메시지를 받습니다.
  • 로그 확인: 각 예외 발생 시 서버 로그에 log.warn 레벨로 해당 예외 메시지가 기록되는지 확인합니다.

의존성 및 환경 설정 변경:

  • 새로운 외부 의존성 패키지가 추가되거나 삭제되지 않았습니다.

  • Docker 설정 및 환경 변수(.env) 설정 변경은 없습니다.

  • 스프링 프레임워크 내장 예외 클래스(org.springframework.dao.DataIntegrityViolationException, org.springframework.security.access.AccessDeniedException 등)에 대한 import만 추가되었습니다.

잠재적 영향도:

  • API 견고성 향상: 클라이언트에게 더욱 명확하고 구체적인 오류 정보를 제공하여 API 사용성을 높입니다.

  • 클라이언트 개발 용이성: 프론트엔드 개발자가 특정 오류 상황에 따라 적절한 UI/UX를 구현하기 용이해집니다.

  • 로그 가독성 개선: 4xx 계열 오류에 대한 로그 레벨을 WARN으로 조정하여, 심각한 시스템 오류(ERROR)와 클라이언트 요청 오류를 명확히 구분할 수 있게 되어 모니터링 및 디버깅 효율성이 향상됩니다.

  • 보안 강화: AccessDeniedException에 대한 명확한 403 응답은 권한 없는 접근 시도를 명확히 인지하고 대응하는 데 도움을 줍니다.


Diagram Walkthrough

flowchart LR
    A[클라이언트 요청] --> B{예외 발생};
    B -- DataIntegrityViolationException --> C[GlobalExceptionHandler];
    B -- AccessDeniedException --> C;
    B -- NoResourceFoundException --> C;
    B -- MissingRequestHeaderException --> C;
    B -- MethodArgumentTypeMismatchException --> C;
    C -- 처리 --> D{정확한 HTTP 응답};
    D -- 409 Conflict --> E1[데이터 제약 위반];
    D -- 403 Forbidden --> E2[접근 거부];
    D -- 404 Not Found --> E3[리소스 없음];
    D -- 400 Bad Request --> E4[잘못된 요청];
Loading

File Walkthrough

Relevant files
Error handling
GlobalExceptionHandler.java
글로벌 예외 핸들러에 5가지 예외 타입 추가 및 로깅 개선                                                 

src/main/java/com/Rootin/global/exception/GlobalExceptionHandler.java

  • DataIntegrityViolationExceptionHttpStatus.CONFLICT (409)로 처리하는 핸들러
    추가
  • AccessDeniedExceptionHttpStatus.FORBIDDEN (403)으로 처리하는 핸들러 추가
  • NoResourceFoundExceptionHttpStatus.NOT_FOUND (404)로 처리하는 핸들러 추가
  • MissingRequestHeaderExceptionHttpStatus.BAD_REQUEST (400)로 처리하는 핸들러
    추가
  • MethodArgumentTypeMismatchExceptionHttpStatus.BAD_REQUEST (400)로
    처리하는 핸들러 추가
  • 4xx 계열 예외 발생 시 log.warn 레벨로 로깅하도록 변경
+45/-0   

@github-actions

Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

🎫 Ticket compliance analysis 🔶

186 - Partially compliant

Compliant requirements:

  • DataIntegrityViolationException 발생 시 409 Conflict 응답 반환
  • AccessDeniedException 발생 시 403 (ErrorCode.FORBIDDEN) 응답 반환
  • NoResourceFoundException 발생 시 404 (ErrorCode.NOT_FOUND) 응답 반환
  • MissingRequestHeaderException 발생 시 400 응답 반환
  • MethodArgumentTypeMismatchException 발생 시 400 Bad Request 응답 반환
  • 예외 발생 시 로그 레벨 정리 (4xx 예외에 대해 WARN 로그 사용)

Non-compliant requirements:

  • MethodNotAllowedException 발생 시 405 응답 반환
⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

메시지 구체화

DataIntegrityViolationException (64-70 라인) 발생 시 반환되는 메시지 "이미 처리된 요청이거나 데이터 제약 조건에 위배됩니다."는 다소 일반적입니다. 특정 제약 조건 위반(예: 고유 제약 조건, 외래 키 제약 조건)에 따라 더 구체적인 메시지를 제공할 수 있다면 사용자에게 더 유용한 정보를 줄 수 있습니다. 현재 구현도 유효하지만, 향후 개선을 고려해볼 수 있습니다.

@ExceptionHandler(DataIntegrityViolationException.class)
public ResponseEntity<ApiResponse<Void>> handleDataIntegrityViolation(DataIntegrityViolationException e) {
    log.warn("DataIntegrityViolationException: {}", e.getMessage());
    return ResponseEntity
            .status(HttpStatus.CONFLICT)
            .body(ApiResponse.error("이미 처리된 요청이거나 데이터 제약 조건에 위배됩니다."));
}

@github-actions

Copy link
Copy Markdown

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
General
에러 메시지 중앙 관리

MissingRequestHeaderExceptionDataIntegrityViolationException 핸들러에서 사용되는 에러 메시지가
하드코딩되어 있습니다. ErrorCode enum에 해당 메시지를 정의하고 활용하면, 모든 에러 메시지를 중앙에서 관리하여 일관성을 확보하고 향후
다국어 지원 등 확장성을 높일 수 있습니다.

src/main/java/com/Rootin/global/exception/GlobalExceptionHandler.java [48-54]

+// ErrorCode enum에 MISSING_HEADER 추가 필요:
+// MISSING_HEADER(HttpStatus.BAD_REQUEST, "필수 헤더가 누락되었습니다: %s")
 @ExceptionHandler(MissingRequestHeaderException.class)
 public ResponseEntity<ApiResponse<Void>> handleMissingRequestHeader(MissingRequestHeaderException e) {
     log.warn("MissingRequestHeaderException: {}", e.getMessage());
+    String errorMessage = String.format(ErrorCode.MISSING_HEADER.getMessage(), e.getHeaderName());
     return ResponseEntity
             .status(HttpStatus.BAD_REQUEST)
-            .body(ApiResponse.error("필수 헤더가 누락되었습니다: " + e.getHeaderName()));
+            .body(ApiResponse.error(errorMessage));
 }
Suggestion importance[1-10]: 8

__

Why: Centralizing error messages in an ErrorCode enum, as suggested, is a good practice for consistency, maintainability, and future internationalization. It prevents hardcoding error messages directly in the handlers.

Medium
동일 응답 예외 핸들러 통합

MethodArgumentTypeMismatchException 핸들러는 HttpMessageNotReadableException 핸들러와 동일하게
HttpStatus.BAD_REQUESTErrorCode.INVALID_INPUT 메시지를 반환합니다. 이처럼 동일한 응답을 반환하는 여러 예외를
하나의 @ExceptionHandler 메서드에서 처리하여 코드 중복을 줄이고 유지보수성을 높일 수 있습니다.

src/main/java/com/Rootin/global/exception/GlobalExceptionHandler.java [56-62]

-@ExceptionHandler(MethodArgumentTypeMismatchException.class)
-public ResponseEntity<ApiResponse<Void>> handleMethodArgumentTypeMismatch(MethodArgumentTypeMismatchException e) {
-    log.warn("MethodArgumentTypeMismatchException: {}", e.getMessage());
+@ExceptionHandler({HttpMessageNotReadableException.class, MethodArgumentTypeMismatchException.class})
+public ResponseEntity<ApiResponse<Void>> handleBadRequestExceptions(Exception e) {
+    log.warn("{}: {}", e.getClass().getSimpleName(), e.getMessage());
     return ResponseEntity
             .status(HttpStatus.BAD_REQUEST)
             .body(ApiResponse.error(ErrorCode.INVALID_INPUT.getMessage()));
 }
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that MethodArgumentTypeMismatchException and HttpMessageNotReadableException handlers return identical responses. Consolidating them into a single handler reduces code duplication and improves maintainability.

Medium

@gyu98-mun

Copy link
Copy Markdown
Collaborator

코드 리뷰

전반적으로 깔끔한 PR입니다. 로그 레벨 분리(4xx → warn, 5xx → error)도 잘 되어 있고요.

🔴 미구현: MethodNotAllowedException (Issue #186 미충족)

이슈 #186 요구사항 중 MethodNotAllowedException → 405 Method Not Allowed 처리가 빠져 있습니다.

@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ResponseEntity<ApiResponse<Void>> handleMethodNotAllowed(HttpRequestMethodNotSupportedException e) {
    log.warn("HttpRequestMethodNotSupportedException: {}", e.getMessage());
    return ResponseEntity
            .status(HttpStatus.METHOD_NOT_ALLOWED)
            .body(ApiResponse.error(ErrorCode.METHOD_NOT_ALLOWED.getMessage()));
}

ErrorCodeMETHOD_NOT_ALLOWED 항목도 추가가 필요합니다.

🟡 Minor: 에러 메시지 하드코딩

MissingRequestHeaderExceptionDataIntegrityViolationException 핸들러만 메시지가 하드코딩되어 있고 나머지는 ErrorCode를 씁니다. 지금 당장 블로커는 아니지만 일관성을 위해 ErrorCode로 통일하는 걸 고려해주세요.


위 405 핸들러 추가 후 LGTM 드리겠습니다 🙏

@noeyoseel

Copy link
Copy Markdown
Collaborator Author

@gyu98-mun 405 핸들러 추가 했습니다. 확인 부탁드려요!

@gyu98-mun gyu98-mun left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM
고생하셨습니다 👍

@noeyoseel noeyoseel merged commit 9e22429 into main Jun 23, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] GlobalExceptionHandler 예외 타입 추가

2 participants