Controller:

@RestController
@RequestMapping(value = "/uritest")
public class TestController {

    @RequestMapping(method = { RequestMethod.GET })
    public ResponseEntity<Void> endpoint(@RequestParam(required = true) String param) {
        System.out.println(param);
        return new ResponseEntity<>(HttpStatus.OK);
    }
}

Integration Test:

@ActiveProfiles("test")
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT)
public class ControllerTest {

    @Autowired
    private TestRestTemplate testRestTemplate;

    @Rule
    public WireMockRule wireMockRule = new WireMockRule();

    HttpHeaders headers;

    @Test
    public void testGivenExistingStateRequestWhenCallGetStateThenReturnOkStatusAndProperBody() throws Exception {
        final UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromPath("/uritest");

        uriBuilder.queryParam("param", "\"\\%");

        final HttpEntity<String> entity = new HttpEntity<>(null, headers);

        final URI uri = uriBuilder.build().toUri();

        testRestTemplate.exchange(uri, HttpMethod.GET, entity, String.class);
        testRestTemplate.exchange(uri.toString(), HttpMethod.GET, entity, String.class);

    }
}

Output:

"\%
%22%5C%25

There is already a closed issue about this bug: https://github.com/spring-projects/spring-boot/issues/8163

I checked the fix there and it adds a root to the URI if the URI was relative. This issue also happens if the provided string describes a relative URI. I assume the same check should be applied to all of the other methods too where the first parameter is a String instead of an URI.

Comment From: mbhave

@Selindek this seems to be happening because the String that gets passed to exchange is already encoded (which causes the double encoding as @wilkinsona mentioned here Instead of using the uri.toString(), if "/uritest?param=\"\\%" is passed, the encoding works fine.

Comment From: spring-projects-issues

If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.

Comment From: spring-projects-issues

Closing due to lack of requested feedback. If you would like us to look at this issue, please provide the requested information and we will re-open the issue.

Comment From: stego39

running into this same issue as well...

Spring Boot 1.5.7 .. using RestTemplate and setting a rootURI using RestTemplateBuilder... the rootURI doesnt get applied when the RestTemplate methods are called (eg getForObject ) when using the URI object... have to use the string version of the URI.. This relates to this issue: #7891 but then we run into the double encoding issue.

A workaround is to simply build the uri string manually and use it directly in the RestTemplate call... seems using URI object encodes the URI at some point... before it's used as a string parameter in the RestTemplate method call. Not as ideal, as I do like using URIComponents to build up my request URIs...

Update: Seems using toUriString() of the UriComponents class in the RestTemplate method call works... string is properly encoded.. once!

Comment From: manish-pcy

Just using new URI(enocodedUrl) in RestTemplateCall solves the issue... UriComponentsBuilder can also be used to prepare encodedUrl from MultiValueMap. Example :

String enocodedUrl = UriComponentsBuilder.fromHttpUrl(URL).queryParams(queryParameterMultiValueMap).build(true).toUriString();

restTemplate.getForEntity(new URI(enocodedUrl ) , String.class);

Comment From: georgmittendorfer

Same issue in spring-boot 2.0.2 using UriComponentsBuilder with TestRestTemplate.

  • uriBuilder.toUriString() encodes space as %2520 (it does: build(false).encode().toUriString()).
  • uriBuilder.build(false).toUriString() encodes space as %20

Comment From: philwebb

@georgmittendorfer Could you provide a small sample to show the problem?

Comment From: georgmittendorfer

Small sample (not very sophisticated):

String uriString = UriComponentsBuilder
                .fromUriString("http://localhost:1234")
                .queryParam("foo", "some value")
                .toUriString();
assertTrue(uriString, uriString.contains("foo=some%20value"));
restTemplate.getForEntity(uriString, String.class);

Listening with netcat -s localhost -l -p 1234 will show that %20 is encoded a second time to %2520:

netcat -s localhost -l -p 1234
GET /?foo=some%2520value HTTP/1.1

Comment From: bclozel

Hi @georgmittendorfer, I think this is the expected behavior in Spring Framework (see SPR-16202 and SPR-14828 for some background on this).

As a general rule, providing a URI String to RestTemplate means that this String should be expanded (using optional method parameters) and then encoded. If you want to take full control over the encoding, you should then use the method variant taking a URI as a parameter.

The Spring Framework team recently added some more documentation on the subject of URI encoding. Does that help?

Comment From: georgmittendorfer

Yes, thank you, that helps. The API is slightly confusing on first contact, though.

Comment From: bclozel

Ok thanks!

Comment From: mathewjustin

@georgmittendorfer Thanks👍

Comment From: Fenzland

Hi @georgmittendorfer, I think this is the expected behavior in Spring Framework (see SPR-16202 and SPR-14828 for some background on this).

As a general rule, providing a URI String to RestTemplate means that this String should be expanded (using optional method parameters) and then encoded. If you want to take full control over the encoding, you should then use the method variant taking a URI as a parameter.

The Spring Framework team recently added some more documentation on the subject of URI encoding. Does that help?

That's really not a considered feature, you can never send a request with RestTemplate like this:

http://example.org/foo?param=%26

If you pass "http://example.org/foo?param=%26" or new URI("http://example.org/foo?param=%26") to RestTemplate, you got http://example.org/foo?param=%2526. But if you pass "http://example.org/foo?param=&", you just got http://example.org/foo?param=&