1. Error Response
- Spring MVC에서의 에러 응답 처리는 RFC 9457 - Problem Details for HTTP APIs를 기반으로 합니다.
2. 핵심 개념 및 클래스
개념 | 설명 |
ProblemDetail |
RFC 9457에 정의된 문제 상세 형식을 표현하는 클래스.
표준 필드와 커스텀 필드를 포함할 수 있음. |
ErrorResponse |
HTTP 상태 코드, 헤더, RFC 9457 형식의 바디를 포함하는 오류 응답 계약.
Spring MVC의 모든 예외가 이를 구현함. |
ErrorResponseException |
ErrorResponse를 구현한 기본 클래스.
사용자 정의 예외에서 상속해 사용 가능. |
ResponseEntityExceptionHandler |
예외를 처리하고 ProblemDetail 형식으로 응답을 렌더링하는 데 사용할 수 있는 기본 클래스.
@ControllerAdvice와 함께 사용됨. |
3. 에러 응답 렌더링 방식
- @ExceptionHandler나 @RequestMapping 메서드에서 ProblemDetail 또는 ErrorResponse 타입의 객체를 반환하면, RFC 9457 규격에 맞는 JSON 에러 응답으로 자동 렌더링됩니다.
예제) @ExceptionHandler 구현
더보기
@ExceptionHandler(ResourceNotFoundException.class)
public ProblemDetail handleNotFound(ResourceNotFoundException ex, HttpServletRequest request) {
ProblemDetail problemDetail = ProblemDetail.forStatus(HttpStatus.NOT_FOUND);
problemDetail.setTitle("리소스를 찾을 수 없습니다");
problemDetail.setDetail(ex.getMessage());
return problemDetail;
}
필드
필드명 | 설명 |
status | HTTP 상태 코드 설정 |
instance |
오류가 발생한 요청의 URL 경로가 자동으로 설정됨 (명시하지 않으면 현재 경로로 설정됨)
|
예시) ProblemDetail 반환 (application/problem+json)
더보기
{
"type": "about:blank",
"title": "리소스를 찾을 수 없습니다",
"status": 404,
"detail": "해당 ID의 사용자를 찾을 수 없습니다",
"instance": "/api/users/123"
}
컨텐츠 협상
- 클라이언트가 요청 헤더에 Accept를 application/problem+json으로 요청하면, Spring은 이를 우선 사용해 RFC 9457 응답 형식으로 응답합니다.
4. 예외 응답 전역 처리 설정
- @ControllerAdvice와 @ExceptionHandler로 전역 예외 처리 설정 가능
예제) HttpRequestMethodNotSupportedException 예외 처리
더보기
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ProblemDetail methodNotFound(HttpRequestMethodNotSupportedException ex) {
ProblemDetail problemDetail = ProblemDetail.forStatus(NOT_FOUND);
problemDetail.setTitle("지원하지 않는 HTTP 메서드 요청입니다.");
problemDetail.setDetail(ex.getMessage());
return problemDetail;
}
}
5. 메시지 국제화
- Spring MVC의 예외는 ErrorResponse 인터페이스 구현체입니다.
- 각 예외 클래스는 국제화 처리를 위한 메시지 코드를 자동으로 보냅니다.
필드
필드명 | 의미 | 메시지 코드 형식 |
type | 해당 문제의 식별자 (URL 형식) | problemDetail.type.[예외 클래스명 (FQCN)] |
title | 간단한 문제 요약. | problemDetail.title.[예외 클래스명 (FQCN)] |
detail | 해당 문제에 대한 구체적인 설명. | problemDetail.[예외 클래스명 (FQCN)][접미사 (optional)] |
- 해당 값들은 MessageSource를 통해 메시지로 변환됩니다.
예제) 메시지 코드 패턴
더보기
problemDetail.type.org.springframework.web.HttpRequestMethodNotSupportedException=https://example.com/errors/method-not-supported
problemDetail.title.org.springframework.web.HttpRequestMethodNotSupportedException=요청 메서드가 지원되지 않습니다
problemDetail.org.springframework.web.HttpRequestMethodNotSupportedException=요청하신 메서드 {0}는 지원되지 않습니다. 지원되는 메서드는 {1}입니다.
예외 별 메시지 코드 인자
예외 클래스 | 메시지 코드 | 메시지 코드 인자 |
AsyncRequestTimeoutException | 기본만 있음 | 없음 |
ConversionNotSupportedException | 기본 | {0} 속성명, {1} 속성값 |
HandlerMethodValidationException | 기본 |
{0} 모든 validation 에러 목록
|
HttpMediaTypeNotAcceptableException | 기본 + .parseError |
{0} 지원되는 미디어 타입 리스트
|
HttpMediaTypeNotSupportedException | 기본 + .parseError |
{0} 지원되지 않는 타입, {1} 지원되는 타입 리스트
|
HttpMessageNotReadableException | 기본 | 없음 |
HttpMessageNotWritableException | 기본 | 없음 |
HttpRequestMethodNotSupportedException | 기본 |
{0} 현재 요청 메서드, {1} 지원되는 메서드 리스트
|
MethodArgumentNotValidException | 기본 |
{0} 글로벌 에러 목록, {1} 필드 에러 목록
|
MissingRequestHeaderException | 기본 | {0} 헤더 이름 |
MissingServletRequestParameterException | 기본 | {0} 파라미터 이름 |
MissingMatrixVariableException | 기본 | {0} 매트릭스 변수 이름 |
MissingPathVariableException | 기본 | {0} 경로 변수 이름 |
MissingRequestCookieException | 기본 | {0} 쿠키 이름 |
MissingServletRequestPartException | 기본 | {0} multipart 파트 이름 |
NoHandlerFoundException | 기본 | 없음 |
NoResourceFoundException | 기본 | 없음 |
TypeMismatchException | 기본 | {0} 속성명, {1} 값 |
UnsatisfiedServletRequestParameterException | 기본 | {0} 파라미터 조건 리스트 |
클라이언트 언어 결정
구분 | 설명 | 예시 |
클라이언트 설정 | 클라이언트가 선호하는 언어를 HTTP 헤더로 서버에 전달함 |
Accept-Language: ko-KR
Accept-Language: en-US Accept-Language: fr-FR |
LocaleResolver | 스프링에서 클라이언트의 Locale을 결정하는 전략을 담당함 |
AcceptHeaderLocaleResolver
Accept-Language를 기반으로 Locale 자동 결정 |
LocaleChangeInterceptor | 특정 파라미터 또는 헤더를 통해 Locale을 변경 가능하게 해주는 인터셉터 |
Accept-Language로 자동 변경
쿼리 파라미터(?lang=ko)로 변경 |
메시지 선택 방식 | MessageSource가 현재 Locale에 맞는 파일을 선택하여 메시지를 제공 messages_{locale}.properties |
Locale이 ko_KR이면 messages_ko.properties 사용
|
예제
예제) HttpRequestMethodNotSupportedException 예외 처리
더보기
1. MessageSource 등록
@Bean
public MessageSource messageSource() {
ResourceBundleMessageSource ms = new ResourceBundleMessageSource();
ms.setBasename("messages");
ms.setDefaultEncoding("UTF-8");
return ms;
}
2. LocaleResolver 등록
@Bean
public LocaleResolver localeResolver() {
return new AcceptHeaderLocaleResolver(); // Accept-Language 헤더에 따라 Locale을 결정
}
3. LocaleChangeInterceptor 추가
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LocaleChangeInterceptor());
}
4. message.properties 파일 생성 및 정의
message_ko.properties
problemDetail.type.org.springframework.web.HttpRequestMethodNotSupportedException=https://example.com/errors/method-not-supported
problemDetail.title.org.springframework.web.HttpRequestMethodNotSupportedException=요청 메서드가 지원되지 않습니다
problemDetail.org.springframework.web.HttpRequestMethodNotSupportedException=요청하신 메서드 {0}는 지원되지 않습니다. 지원되는 메서드는 {1}입니다.
message_en.properties
problemDetail.type.org.springframework.web.HttpRequestMethodNotSupportedException=https://example.com/errors/method-not-supported
problemDetail.title.org.springframework.web.HttpRequestMethodNotSupportedException=HTTP method not supported
problemDetail.org.springframework.web.HttpRequestMethodNotSupportedException=The requested method {0} is not supported. Supported methods are {1}.
5. 전역 예외 처리 컨트롤러에 핸들러 등록
@RestControllerAdvice
public class GlobalExceptionHandler {
private MessageSource messageSource;
public GlobalExceptionHandler(MessageSource messageSource) {
this.messageSource = messageSource;
}
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ResponseEntity<ProblemDetail> methodNotFound(HttpRequestMethodNotSupportedException ex, Locale locale) {
String type = messageSource.getMessage("problemDetail.type."+ex.getClass().getName(), null, locale);
String title = messageSource.getMessage("problemDetail.title."+ex.getClass().getName(), null, locale);
String detail = messageSource.getMessage("problemDetail."+ex.getClass().getName(), new Object[]{ex.getMethod(), toStringDelimiter(ex.getSupportedMethods())}, locale);
ProblemDetail problemDetail = ProblemDetail.forStatus(METHOD_NOT_ALLOWED);
problemDetail.setType(URI.create(type));
problemDetail.setTitle(title);
problemDetail.setDetail(detail);
return new ResponseEntity<>(problemDetail, METHOD_NOT_ALLOWED);
}
private String toStringDelimiter(String[] supportedMethods) {
return Arrays.stream(supportedMethods).collect(Collectors.joining(", "));
}
}
6. 클라이언트 예외 처리
- 클라이언트 측에서는 다음 예외를 활용해 에러 응답을 파싱할 수 있습니다:
클라이언트 | 예외 클래스 | 설명 |
WebClient | WebClientResponseException | HTTP 상태 코드가 4xx 또는 5xx인 응답을 받을 때 발생 |
RestTemplate | RestClientResponseException | RestTemplate 호출 중 4xx/5xx 오류가 발생했을 때 발생하는 최상위 예외 클래스 |
출처
'Spring > Spring MVC' 카테고리의 다른 글
[Tomcat] 1. Introduction (0) | 2025.04.14 |
---|---|
[Spring MVC] 7. HTTP Caching (0) | 2025.04.14 |
[Spring MVC] 3. HTTP Message Conversion (0) | 2025.04.12 |
[Spring MVC] 2. Filters (0) | 2025.04.11 |
[Spring MVC] 4-3. Handler Methods: Controller Advice (0) | 2023.10.17 |