Martin Schröder
Martin Schröder

Reputation: 4591

RestTemplate URI template syntax for collections?

I have a Spring Boot 2 service with a method

@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)

I want to call that service from another Spring Boot service. For this I do

HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
val response =
    restTemplate.exchange(
        "http://castor-v2/providers/usable/search?network-ids[]={0}&usages[]={1}",
        HttpMethod.GET,
        new HttpEntity<Long>(httpHeaders),
        new ParameterizedTypeReference<List<Provider>>() {},
        Collections.singletonList(networkId),
        Collections.singleton(Usage.MESSAGE_DELIVERY));

This generates a http request like search?network-ids[]=[428]&usages[]=[MESSAGE_DELIVERY] which is wrong (the server bombs with org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'java.util.List'; nested exception is java.lang.NumberFormatException: For input string: "[573]"); correct would be search?network-ids[]=77371&usages[]=MESSAGE_DELIVERY.

Most likely the URI template is wrong. How should it be to use with Java collections?

Update: I created a new api endpoint without the brackets and used UriComponentsBuilder as suggested by @vatsal.

Upvotes: 0

Views: 1786

Answers (3)

vatsal gosar
vatsal gosar

Reputation: 187

To easily manipulate URLs / path / params / etc., you can use Spring's UriComponentsBuilder class. It's cleaner that manually concatenating strings and it takes care of the URL encoding for you:

Simple code to do this using RestTemplate will be as follows:

HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);

String url = "http://castor-v2/providers/usable/search";
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url)
            .queryParam("country-id", countryId)
            .queryParam("network-ids[]", networkId)
            .queryParam("usages[]", Usage.MESSAGE_DELIVERY);

HttpEntity<?> entity = new HttpEntity<>(httpHeaders);

HttpEntity<String> response = restTemplate.exchange(
            builder.toUriString(),
            HttpMethod.GET,
            entity,
            String.class);

This should resolve your query. Also, make sure you pass Usage as a String. If you still want to continue using Usage as an Object then instead of RequestParam you can use RequestBody and pass it as Body to the POST call.

Upvotes: 2

Hypnotise
Hypnotise

Reputation: 109

It probably converts the singletonList to a string. Which will include the block brackets. So you can either convert the list to a string beforehand or create your own list implementation which has a to string method that converts the values to a comma separated list.

Upvotes: 1

Rowi
Rowi

Reputation: 565

You cannot pass a object as request param. Request parameters are a Multi values map of String to String. if you want to pass Usage as String You can create method like this

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

To call this service

http://castor-v2/providers/usable/search?networkIds=0&networkIds=1&usages=usages1&usages=usages2

Upvotes: 1

Related Questions