'spring resttemplate url encoding

I try to do a simple rest call with springs resttemplate:

private void doLogout(String endpointUrl, String sessionId) {
    template.getForObject("http://{enpointUrl}?method=logout&session={sessionId}", Object.class,
            endpointUrl, sessionId);
}

Where the endpointUrl variable contains something like service.host.com/api/service.php

Unfortunately, my call results in a org.springframework.web.client.ResourceAccessException: I/O error: service.host.com%2Fapi%2Fservice.php

So spring seems to encode my endpointUrl string before during the creation of the url. Is there a simple way to prevent spring from doing this?

Regards



Solution 1:[1]

Depends on which version of Spring you're using. If your version is too old, for example, version 3.0.6.RELEASE, you'll not have such facility as UriComponentsBuilder with your spring-web jar.

What you need is to prevent Spring RestTemplate from encoding the URL. What you could do is:

import java.net.URI;

StringBuilder builder = new StringBuilder("http://");
builder.append(endpointUrl);
builder.append("?method=logout&session=");
builder.append(sessionId);

URI uri = URI.create(builder.toString());
restTemplate.getForObject(uri, Object.class);

I tested it with Spring version 3.0.6.RELEASE, and it works.

In a word, instead of using restTemplate.getForObject(String url, Object.class), use restTemplate.getForObject(java.net.URI uri, Object.class)

See the rest-resttemplate-uri section of the Spring document

Solution 2:[2]

Looks like I found best native way (up-to-date) solution:

  1. Do not pass encoded url string as parameter to RestTemplate.exchange()
  2. Use URI object instead. Use UriComponentsBuilder to construct URI.

See (simplified) example below:

    String instanceUrl = "https://abc.my.salesforce.com"
    HttpEntity<String> entity = new HttpEntity<>(headers);
    UriComponents uriComponents =
            UriComponentsBuilder.fromHttpUrl(instanceUrl)
                    .path("/services/data/v45.0/query/")
                    .queryParam("q", String.format(sqlSelect, id))
                    .build();

    ResponseEntity<OpportunityLineItem> responseEntity =
            restTemplate.exchange(
                    uriComponents.toUri(), HttpMethod.GET,
                    entity, OpportunityLineItem.class);

// Wrong! URI string will be double encoded
/*
ResponseEntity<OpportunityLineItem> responseEntity =
            restTemplate.exchange(
                    uriComponents.toUriString(), HttpMethod.GET,
                    entity, OpportunityLineItem.class);
*/

This way you will not get issue with double encoding.

Solution was found while debugging SalesForce REST client, based on Spring RestTemplate client (including SOQL queries).

Solution 3:[3]

You can use the overloaded variant that takes a java.net.URI instead public T getForObject(URI url, Class responseType) throws RestClientException

From Spring's own documentation

UriComponents uriComponents =
    UriComponentsBuilder.fromUriString("http://example.com/hotels/{hotel}/bookings/{booking}").build()
        .expand("42", "21")
        .encode();

URI uri = uriComponents.toUri();

Solution 4:[4]

Full example with headers, body, for any HttpMethod and ResponseType could look like:

String url = "http://google.com/{path}?param1={param1Value}&param2={param2Value}";
Object body = null;
HttpEntity request = new HttpEntity(body, new HttpHeaders());

Map<String, String> uriVariables = new HashMap<>();
uriVariables.put("path", "search");
uriVariables.put("param1Value", "value1");
uriVariables.put("param2Value", "value2");

ResponseEntity<Void> responseEntity = restTemplate.exchange(url, HttpMethod.POST, request, Void.class, uriVariables)
//responseEntity.getBody()

Actually, it will use the same UriTemplate and expand method

Solution 5:[5]

Apparently there is a better way to do this by calling build(true) of class UriComponentsBuilder:

private void doLogout(String endpointUrl, String sessionId) {
    String url = "http://" + endpointUrl +"?method=logout&session=" + + URLEncoder.encode(sessionId, "UTF-8");
    URI uri = UriComponentsBuilder.fromUriString(url.toString()).build(true).toUri();
    template.getForObject(uri, Object.class,
            endpointUrl, sessionId);
}

This method tells URIComponentsBuilder not to encode while creating URI.

Solution 6:[6]

Best way to do is with UriComponentsBuilder:

UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
builder.queryParam("some_param", "param with space for encoding");
template.getForObject(builder.encode().build().toUri(), Object.class, headers);

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 Andrii Abramov
Solution 2
Solution 3 Not a code monkey
Solution 4
Solution 5 Tabish Khan
Solution 6 Tomislav Brabec