Spring bootで@DateTimeFormatのバリデーションで発生する例外を調査

## 環境

* Spring boot 3.2.4
* Amazon Corretto JDK 17

## 調査したこと

* Controllerで日付のパラメタを受け取るときに、バリデーションをかけたい
* @DateTimeFormatと@JsonFormatは@Sizeなどの通常のバリデーションとスローされる例外が異なっていた
* @RequestParamと@RequestBodyの位置の違いでも動作が異なるため、それぞれ試してみる

## 結果

|位置|バリデーション|スローされる例外|
|-|-|-|
|@PathVariable|@Size|ConstraintViolationException|
|@PathVariable|@DateTimeFormat|MethodArgumentTypeMismatchException|
|@RequestParam|@Size|ConstraintViolationException|
|@RequestParam|@DateTimeFormat|MethodArgumentTypeMismatchException|
|@RequestBody|@Size|MethodArgumentNotValidException|
|@RequestBody|@JsonFormat|HttpMessageNotReadableException|

## 検証する

### 検証用のコード

```java:SandboxController.java
@RestController
@Validated
public class SandboxController {

    @PostMapping("/echo/{pathText}/{pathTime}")
    public ResponseEntity<EchoResponse> postEcho(
        @PathVariable(name = "pathText") @Size(min = 1, max = 10) String pathText,
        @PathVariable(name = "pathTime") @DateTimeFormat(pattern = "yyyy-MM-dd_HH:mm:ss") LocalDateTime pathTime,
        @RequestParam(name = "paramText") @Size(min = 1, max = 10) String paramText,
        @RequestParam(name = "paramTime") @DateTimeFormat(pattern = "yyyy-MM-dd_HH:mm:ss") LocalDateTime paramTime,
        @RequestBody @Validated EchoRequest request
    ){

        EchoResponse res = new EchoResponse(paramText, paramTime);
        return new ResponseEntity<EchoResponse>(res, HttpStatus.OK);
    }

}
```

```java:EchoRequest.java
@Getter
public class EchoRequest {

    @Size(min = 1, max = 10)
    String bodyText;

    //Bodyでは@DateTimeFormatが効かないので、@JsonFormatを使用する
    //@DateTimeFormat(pattern = "yyyy-MM-dd_HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd_HH:mm:ss")
    LocalDateTime bodyTime;
}
```


```java:EchoResponse.java
@Getter
@Setter
@AllArgsConstructor
public class EchoResponse {
    String text;
    LocalDateTime time;
}
```

```java:ControllerExceptionHandler.java
@RestControllerAdvice
public class ControllerExceptionHandler {

    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleAnyException(Exception e){
        e.printStackTrace();
        String reason = e.getClass().toString();

        return new ResponseEntity<>(reason, HttpStatus.INTERNAL_SERVER_ERROR);
    }

}
```

#### パターンに一致する場合

```json
{
    "text": "1234567890",
    "time": "2024-12-31T00:00:00"
}
```

#### @PathVariableの@Sizeが通過できなかった場合

* ConstraintViolationException がスローされた

```http
POST /echo/12345678901/2024-12-31_00:00:00?paramText=1234567890&paramTime=2024-12-31_00:00:00 HTTP/1.1
{
"bodyText": "1234567890",
"bodyTime": "2024-12-31_00:00:00"
}
 
HTTP/1.1 500 Internal Server Error
class jakarta.validation.ConstraintViolationException
```

#### @PathVariableの@DateTimeFormatが通過できなかった場合

* MethodArgumentTypeMismatchException がスローされた

```http
POST /echo/1234567890/2024-12-31_00:00:0000?paramText=1234567890&paramTime=2024-12-31_00:00:00 HTTP/1.1
{
"bodyText": "1234567890",
"bodyTime": "2024-12-31_00:00:00"
}
 
HTTP/1.1 500 Internal Server Error
class org.springframework.web.method.annotation.MethodArgumentTypeMismatchException
```

#### @RequestParamの@Sizeが通過できなかった場合

* ConstraintViolationException がスローされた

```http
POST /echo/1234567890/2024-12-31_00:00:00?paramText=12345678901&paramTime=2024-12-31_00:00:00 HTTP/1.1
{
"bodyText": "1234567890",
"bodyTime": "2024-12-31_00:00:00"
}
 
HTTP/1.1 500 Internal Server Error
class jakarta.validation.ConstraintViolationException
```

#### @RequestParamの@DateTimeFormatが通過できなかった場合

* MethodArgumentTypeMismatchException がスローされた

```http
POST /echo/1234567890/2024-12-31_00:00:00?paramText=1234567890&paramTime=2024-12-31_00:00:0000 HTTP/1.1
{
"bodyText": "1234567890",
"bodyTime": "2024-12-31_00:00:00"
}
 
HTTP/1.1 500 Internal Server Error
class org.springframework.web.method.annotation.MethodArgumentTypeMismatchException
```

#### @RequestBodyの@Sizeが通過できなかった場合

* MethodArgumentNotValidException がスローされた

```http
POST /echo/1234567890/2024-12-31_00:00:00?paramText=1234567890&paramTime=2024-12-31_00:00:00 HTTP/1.1
{
"bodyText": "12345678901",
"bodyTime": "2024-12-31_00:00:00"
}
 
HTTP/1.1 500 Internal Server Error
class org.springframework.web.bind.MethodArgumentNotValidException
```

#### @RequestBodyの@DateTimeFormatが通過できなかった場合

* HttpMessageNotReadableException がスローされた

```http
POST /echo/1234567890/2024-12-31_00:00:00?paramText=1234567890&paramTime=2024-12-31_00:00:00 HTTP/1.1
{
"bodyText": "1234567890",
"bodyTime": "2024-12-31_00:00:0000"
}
 
HTTP/1.1 500 Internal Server Error
class org.springframework.http.converter.HttpMessageNotReadableException
```