@PathVariable의 값이 URL 경로에서 누락된 경우 발생한다.@GetMapping("/posts/{id}")
public String getPost(@PathVariable Long id) { ... }
위 API 사용할 경우 URL경로를 /posts/{id}로 보내야 하는데, 이 때 /posts로 보내고 id값이 누락되는 경우 MissingPathVariableException이 발생한다.
@RequestParam이 필수인데 요청에서 누락된 경우 발생한다.@GetMapping("/search")
public String search(@RequestParam String keyword) { ... }
위 코드의 경우 @RequestParam을 사용했으므로 API호출 시 /search?keyword="??"와 같은 형식으로 보내야 한다. 이 때 keyword값이 누락된 경우 MissingServletRequestParameterException이 발생한다.
@GetMapping("/posts/{id}")
public String getPost(@PathVariable Long id) { ... }
위와 같은 코드에서 /posts/{id}의 id값은 Long타입이다. 그러나 API 호출 시 /posts/abc이와 같이 호출한 경우 MethodArgumentTypeMismatchException이 발생한다.
@PostMapping("/posts")
public String create(@RequestBody PostDto dto) { ... }
위 코드는 @RequestBody를 사용하였으므로 요청 시 Body를 함께 보내주어야 한다. 이 때 요청 시 Body가 비어있거나 { title: "hello", content: }처럼 요청값이 잘못된 경우 HttpMessageNotReadableException가 발생한다.
예를들어 컨트롤러에선 json만 반환이 가능한데, 클라이언트에서 xml형식으로 요청을 하게 될 경우 xml을 제공할 수 없으므로 HttpMediaTypeNotAcceptableException 예외가 발생한다.
Controller가 아예 없을 때 발생한다. 프로그램의 컨트롤러 중 해당 URL을 처리할 수 있는 핸들러가 없을 때 NoResourceFoundException예외가 발생한다.
URL은 특정 HTTP 메서드만 허용하는데, 다른 메섣드로 호출한 경우 발생한다.@GetMapping("/posts")
public String getPosts() { ... }
위 코드는 @GetMapping을 사용했기 때문에 호출 시 GET 메서드로 호출해야 한다. 이 때 클라이언트에서 GET이 아닌 다른 메서드로 API를 호출할 경우 HttpRequestMethodNotSupportedException예외가 발생한다.
@PostMapping("/posts")
public String create(@RequestBody PostDto dto) { ... }
위 코드의 경우 @RequestBody를 사용했으므로 JSON형태로 API를 호출해야 한다. 이 때 컨텍스트 타입이 JSON이 아닌 다른 타입일 경우 HttpMediaTypeNotSupportedException예외가 발생한다.
위와 같은 예외들을 각각의 컨트롤러에서 처리하게 될 경우 만일 컨트롤러의 수가 수십가지 이상이라면, 수십가지 이상의 중복된 작업을 해야한다. 따라서 해당 예외들을 한 번에 처리할 수 있는 GlobalExceptionHandler를 생성하여 이곳에서 예외를 관리 및 처리한다.
@RestControllerAdvice 또는 @ControllerAdvice를 이용한다.
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public ResponseEntity<ErrorResponse> handleTypeMismatch(MethodArgumentTypeMismatchException ex) {
return buildError("잘못된 타입의 파라미터입니다.", HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<ErrorResponse> handleUnreadable(HttpMessageNotReadableException ex) {
return buildError("요청 본문을 읽을 수 없습니다.", HttpStatus.BAD_REQUEST);
}
private ResponseEntity<ErrorResponse> buildError(String message, HttpStatus status) {
return new ResponseEntity<>(new ErrorResponse(status.value(), message), status);
}
public static class ErrorResponse {
private int status;
private String message;
public ErrorResponse(int status, String message) {
this.status = status;
this.message = message;
}
// getter/setter 생략 가능
}
}
@ExceptionHandler로 처리할 예외 클래스를 지정해준 뒤, 매개변수로 입력받는다.
응답 형식을 통일하고싶다면, 다음과 같은 DTO클래스를 생성한다.
public class ErrorResponse {
private int status;
private String message;
public ErrorResponse(int status, String message) {
this.status = status;
this.message = message;
}
}
이 후 예외 발생 지점에 해당 기능을 예외로 던지면 된다.