'How to avoid double-encoding of [] when using Spring RestTemplate?

I've a Spring Boot 2.2.5 application calling this REST endpoint

@RequestMapping(path = "/usable/search")
public List<Provider> findUsable(
      @RequestParam(name = "country-id", required = false) Integer countryId,
      @RequestParam(name = "network-ids[]", required = false) List<Integer> networkIds,
      @RequestParam(name = "usages[]") Set<Usage> usages)

My caller does

val url = clientUri.toString() + configuration.getUrl().getUseableProvidersForNetworks(); % 'providers/usable/search?'
val builder = UriComponentsBuilder.fromHttpUrl(url)
      .queryParam("usages[]", USAGES)
      .queryParam("network-ids[]", networkIds);
val response =
      restTemplate.exchange(
          builder.toUriString(),
          HttpMethod.GET,
          entity,
          new ParameterizedTypeReference<List<Provider>>() {});

where USAGES is a Set<Usage> and networkIds is a List<Integer>.

The RestTemplate is created by

RestTemplate eurekaRestTemplate() {
    val requestFactory = new HttpComponentsClientHttpRequestFactory();
    requestFactory.setReadTimeout(TIMEOUT);
    requestFactory.setConnectTimeout(TIMEOUT);
    return new RestTemplate(new BufferingClientHttpRequestFactory(requestFactory));
  }

Problem: The [] get double encoded to %255B%255D and I end up with a call to

providers/usable/search?usages%255B%255D=MESSAGE_DELIVERY&network-ids%255B%255D=2145118475&network-ids%255B%255D=-1536358371

Which obviously doesn't work; the server misses e.g. usages[]. It works with curl with [] instead of %255B%255D.

How can I avoid that?

I'm aware of this issue and the docs, but that didn't help yet.



Solution 1:[1]

The solution was to call UriComponentsBuilder.build(false) to instruct the UriComponentsBuilder not to encode the url:

val response = restTemplate.exchange(
              builder.build(false).toUriString(),
              HttpMethod.GET,
              entity,
              new ParameterizedTypeReference<List<Provider>>() {});

This is tricky because a test with Mockito/JUnit of this will tell you that the url is not encoded while a test with MockRestServiceServer will show one level of encoding ([] become %5B%5D).

Solution 2:[2]

I think you shouldn't be dealing with using []. I mean, it's not necessary to name your parameters using "[]". Why would you do that?

val builder = UriComponentsBuilder.fromHttpUrl(url)
      .queryParam("usages", USAGES)
      .queryParam("network-ids", networkIds);

will perfectly work for:

public List<Provider> findUsable(
      @RequestParam(name = "country-id", required = false) Integer countryId,
      @RequestParam(name = "network-ids", required = false) List<Integer> networkIds,
      @RequestParam(name = "usages") Set<Usage> usages)

Hope it helps!

Hint: be careful when using array/collection parameters on URLs, there's a URL length limit that you must consider

Solution 3:[3]

If you wish to pass the encoded url directly to RestTemplate, you can instruct RestTemplate to not encode the url by passing the java.net.URI instead of a String. Here's an example using org.springframework.web.util.UriComponents

String url = "https://someurl/this%2Fis%2Fencoded/";
UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl(url).build(true);
ResponseEntity<String[]> file = template.exchange(uriComponents.toUri(), 
HttpMethod.GET, entity,String[].class);

By passing UriComponentsBuilder.fromHttpUrl(url).build(true) You are essentially letting UriComponents know that your url is already encoded.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Martin Schröder
Solution 2 develo
Solution 3 Saideep Ullal