Encoding of URI Variables on RestTemplate
Last updated: January 12, 2023
1. Overview
In this tutorial, we’ll learn how to encode URI variables on Spring’s RestTemplate.
One of the common encoding problems that we face is when we have a URI variable that contains a plus sign (+). For example, if we have a URI variable with the value http://localhost:8080/api/v1/plus+sign, the plus sign will be encoded as a space, which may result in an unexpected server response.
Let’s look at a few ways to solve this.
2. Project Setup
We’ll create a small project that uses RestTemplate to call an API.
2.1. Spring Web Dependency
Let’s start by adding the Spring Web Starter dependency to our pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Alternatively, we can use the Spring Initializr to generate the project and add the dependency.
2.2. RestTemplate Bean
Next, we’ll create a RestTemplate bean:
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
3. API Call
Let’s create a service class that calls the public API http://httpbin.org/get.
The API returns a JSON response with the request parameters. For example, if on the browser we call the URL https://httpbin.org/get?parameter=springboot, we get this response:
{
"args": {
"parameter": "springboot"
},
"headers": {
},
"origin": "",
"url": ""
}
Here, the args object contains the request parameters. Other values are omitted for brevity.
3.1. Service Class
Let’s create a service class that calls the API and returns the value of the parameter key:
@Service
public class HttpBinService {
private final RestTemplate restTemplate;
public HttpBinService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public String get(String parameter) {
String url = "http://httpbin.org/get?parameter={parameter}";
ResponseEntity<Map> response = restTemplate.getForEntity(url, Map.class, parameter);
Map<String, String> args = (Map<>) response.getBody().get("args");
return args.get("parameter");
}
}
The get() method calls the specified URL, parses the response into a Map, and retrieves the value of the field parameter inside the args object.
3.2. Testing
Let’s test our service class for two parameters – springboot and spring+boot – and check if the response is as expected:
@SpringBootTest
class HttpBinServiceTest {
@Autowired
private HttpBinService httpBinService;
@Test
void givenWithoutPlusSign_whenGet_thenSameValueReturned() throws JsonProcessingException {
String parameterWithoutPlusSign = "springboot";
String responseWithoutPlusSign = httpBinService.get(parameterWithoutPlusSign);
assertEquals(parameterWithoutPlusSign, responseWithoutPlusSign);
}
@Test
void givenWithPlusSign_whenGet_thenSameValueReturned() throws JsonProcessingException {
String parameterWithPlusSign = "spring+boot";
String responseWithPlusSign = httpBinService.get(parameterWithPlusSign);
assertEquals(parameterWithPlusSign, responseWithPlusSign);
}
}
If we run the tests, we’ll see that the second test fails. The response is spring boot instead of spring+boot.
4. Using Interceptors With RestTemplate
We can use an interceptor to encode the URI variables.
Let’s create a class that implements the ClientHttpRequestInterceptor interface:
public class UriEncodingInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
HttpRequest encodedRequest = new HttpRequestWrapper(request) {
@Override
public URI getURI() {
URI uri = super.getURI();
String escapedQuery = uri.getRawQuery().replace("+", "%2B");
return UriComponentsBuilder.fromUri(uri)
.replaceQuery(escapedQuery)
.build(true).toUri();
}
};
return execution.execute(encodedRequest, body);
}
}
We’ve implemented the intercept() method. This method will be executed before the RestTemplate makes each request.
Let’s break down the code:
- We created a new HttpRequest object that wraps the original request.
- For this wrapper, we override the getURI() method to encode the URI variables. In this case, we replace the plus sign with %2B in the query string.
- Using the UriComponentsBuilder, we create a new URI and replace the query string with the encoded query string.
- We return the encoded request from the intercept() method that will replace the original request.
4.1. Adding the Interceptor
Next, we need to add the interceptor to the RestTemplate bean:
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setInterceptors(Collections.singletonList(new UriEncodingInterceptor()));
return restTemplate;
}
}
If we run the test again, we’ll see that it passes.
Interceptors provide the flexibility to change any parts of the requests we want. They can be beneficial for complex scenarios like adding extra headers or performing changes to the fields in the request.
For simpler tasks like our example, we can also use the DefaultUriBuilderFactory to alter the encoding. Let’s see how to do that next.
5. Using DefaultUriBuilderFactory
Another way to encode the URI variables is by altering the DefaultUriBuilderFactory object internally used by the RestTemplate.
By default, the URI builder first encodes the entire URL and then encodes the values separately. We’ll create a new DefaultUriBuilderFactory object and set the encoding mode to VALUES_ONLY. This limits the encoding to values only.
We can then use the setUriTemplateHandler() method to set the new DefaultUriBuilderFactory object in our RestTemplate bean.
Let’s use this to create a new RestTemplate bean:
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
DefaultUriBuilderFactory defaultUriBuilderFactory = new DefaultUriBuilderFactory();
defaultUriBuilderFactory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY);
restTemplate.setUriTemplateHandler(defaultUriBuilderFactory);
return restTemplate;
}
}
This is another alternative to encode the URI variables. Again, if we run the test, we’ll see that it passes.
6. Conclusion
In this article, we saw how to encode the URI variables in a RestTemplate request. We saw two ways to do this — using an interceptor and altering the DefaultUriBuilderFactory object.
As always, the code examples used in this article can be found over on GitHub.