@RequestParam으로 ZonedDateTime 요청 시 이슈

2024. 2. 11. 15:34Java

이슈 내용

환경: JDK 1.8, Spring Boot 2.7.x
Controller에서 @RequestParam으로 ZonedDateTime(”2024-01-01T00:00:00.000+09:00”)을 요청 받을 경우 parsing 에러 발생


에러 로그

.w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'java.time.ZonedDateTime'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [@org.springframework.web.bind.annotation.RequestParam java.time.ZonedDateTime] for value '2024-01-01T00:00:00.000+09:00'; nested exception is java.lang.IllegalArgumentException: Parse attempt failed for value [2024-01-01T00:00:00.000+09:00]]


컨트롤러

@RestController
@RequestMapping("/api/v1")
public class TestController {

    @GetMapping
    public TestRs findCurrentTime(@RequestParam ZonedDateTime date) {
        return new TestRs("response", date);
    }

}

실행한 테스트 코드

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class TestControllerTest {

    @LocalServerPort
    int port;

    @BeforeEach
    public void setup() {
        RestAssured.port = port;
    }

    @Test
    public void testFindCurrentTime() {
        String date = "2024-01-01T00:00:00.000+09:00";
        given()
                .queryParam("date", date)
                .when()
                .get("/api/v1")
                .then()
                .statusCode(200)
                .contentType(ContentType.JSON)
                .body("message", equalTo("response"))
                .body("date", equalTo(date));
    }
}

원인 분석

이러한 현상에 대해 디버깅을 돌려보면, java.time 모듈 내 DateTimeFormatter에서 parsing 처리를 하고 있는 것을 알 수 있다.

어디서 parsing이 이루어지는 지 몰랐기에, DispatcherServlet 부터 빠르게 넘겨가며 찾을 수 있었다.



DateTimeFormatterprintParserCompositePrinterParser (DateTimePrinterParser 구현체) 이고, parse(DateTimeParseContext context, CharSequence text, int position) 메서드를 확인해보면 다음과 같습니다.



아무런 설정하지 않은 상태에서의 optional 값은 false 이므로, 네모박스 영역의 로직에서 실제 parsing이 일어납니다.

여기서, CompositePrinterParser 가 가진 DateTimePrinterParser[] printerParsers 에 따라 parsing이 결정됩니다.

디버깅을 통해 확인해보면 다음과 같습니다.



이후 LocalizedPrintParser 구현체의 parse(DateTimeParseContext context, CharSequence text, int position) 메서드 내에서 formatter(context.getLocale(), chrono).toPrinterParser(false) 부분이 다음과 같은 printerParsers를 가진 CompositePrintParser를 반환합니다.



이대로 진행하는 경우, 2024-01-01T00:00:00.000+09:00 입력에 대해서 위 이미지에서 보이는 printParsers[1]CharLiteralPrintParser 가 202 부분을 처리하는 과정에서 에러가 발생합니다.

결국, java.time 모듈에서 기본적으로 설정되는 ZonedDateTime 관련 포맷팅이 우리가 기대한 것과 다른 것을 알 수 있습니다.

이에 대한 해결책은 여러 개발자 분들의 포스팅을 통해 도움을 받았습니다. (문서 하기 링크 참고)

  • @RequestParam 인자에 @DateTimeFormatter 추가
  • WebMvcConfigurer 구현체를 통해 registry 추가


해결책 1. @RequestParam 인자에 @DateTimeFormatter 추가

다음과 같이 특정 컨트롤러 메서드 인자에 @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX") 를 추가해주는 것입니다.

@GetMapping
public TestRs findCurrentTime(@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX") ZonedDateTime date) {
    return new TestRs("response", date);
}

다음과 같이 printerParser가 구성되며, 테스트가 정상적으로 통과합니다.



해결책 2. WebMvcConfigurer 구현체를 통해 커스텀 Converter 추가

다음과 같이 WebMvcConfigurer를 구현한 Config 클래스에 StringZonedDateTime 변환하는 역할을 하는 Converter 구현체를 추가합니다.

다음과 같이 WebMvcConfigurer를 구현한 Config 클래스에 StringZonedDateTime 변환하는 역할을 하는 Converter 구현체를 추가합니다.

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new StringToZonedDateTimeConverter());
    }
}
public class StringToZonedDateTimeConverter implements Converter<String, ZonedDateTime> {

    @Override
    public ZonedDateTime convert(String source) {
        return ZonedDateTime.parse(source, DateTimeFormatter.ISO_DATE_TIME);
    }
}

다음과 같이 printerParser가 구성되며 정상적으로 통과하는 것을 확인할 수 있습니다.



참고 링크

'Java' 카테고리의 다른 글

성능 이슈 발생한 무거운 API 튜닝하기  (0) 2023.05.14