Mate Szilard
Mate Szilard

Reputation: 108

UriComponentsBuilder cannot encode ISO-8601 date parameters

I encountered an interesting problem while working on some integration tests.

I have a rest endpoint which has some query parameters which are time stamps specified in ISO-8601 (ex: 2020-07-08T23:54:36.931159+03:00). The dates are formatted using java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME

I create the request address by using:

//Query params are supplied in a Map<String,String>
String baseURL="http://localhost:" + this.port + uri
    UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(baseURL);
    if (params != null) {
      for (final Entry<String, String> entry : params.entrySet()) {
        builder.queryParam(entry.getKey(), entry.getValue());
      }
    }

This will generate the following address:

http://localhost:8090/remainingAddress?fromDate=2020-07-08T23:54:36.834869+03:00&toDate=2020-07-08T23:54:36.931159+03:00

The problem is that the "+" sign was not encoded and stayed as it was originally present in the Map. On the receiving end the parameters are decoded thus resulting in the following date "020-07-08T23:54:36.834869 03:00" (notice the space instead of the +) and because of this when I attempt to parse the date using the same formatter it fails.

I tried encoding the parameters with java.net.URLEncoder.encode(String, Charset) before I add the value to the builder:

  UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(baseURL);
    if (params != null) {
      for (final Entry<String, String> entry : params.entrySet()) {
        var encoded=URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8);
        builder.queryParam(entry.getKey(), encoded);
      }
    }

However this introduces "%" as a special character which actually triggers encoding in the builder thus the date parameter is encoded 2 times.

input string: 2020-07-09T00:14:15.230699+03:00
result after first encoding: 2020-07-09T00%3A14%3A15.230699%2B03%3A00
final result: http://localhost:8090/remainingAddress?fromDate=2020-07-09T00%253A14%253A15.230699%252B03%253A00&toDate=2020-07-09T00%253A14%253A15.31158%252B03%253A00

Upon execution of the request,I receive the following input when trying to parse the date: 2020-07-09T00%253A14%253A15.230699%252B03%253A00 (quite a mess).

Note this project uses Spring Boot V2.5.5.

My question is how should I offer the query parameters so they will be encoded correctly? Or perhaps this builder is not meant to be used with special characters?

Upvotes: 2

Views: 1407

Answers (1)

bh4r4th
bh4r4th

Reputation: 4430

NOTE: This is Kotlin implementation

I came across this issue recently,

Where one of my endpoints have range limits as queryparam with type OffsetDateTime.

Which is sometimes like a UTC timestamp e.g., 2021-01-13T02:00Z or with a zone offset like 2021-01-13T13:00+11:00 where both equals to 2021-01-13T13:00 LocalDateTime of ZoneId Australia/Melbourne.

Initially, I tried to build uri like:

val uri = UriComponentsBuilder.fromUriString(baseApi.url)
        .path("/v1/products/${productId}/orders")
        .queryParam("from", from)
        .queryParam("to", to)
        .queryParam("type", type)
        .build().encode().toUri()

Which works fine with UTC timestamps but not encoding timestamps with offset. Like in your case.

Then this is what fixed for me:

val uri = UriComponentsBuilder.fromUriString(baseApi.url)
        .path("/v1/products/${productId}/orders")
        .queryParam("from", "{from}")
        .queryParam("to", "{to}")
        .queryParam("type", type)
        .encode().buildAndExpand(from, to).toUri()

This helps to inject OffsetDateTime as variable into string while buildAndExpand. String interpolation here fixes any uriString issues by adding those special escape characters.

Upvotes: 1

Related Questions