Partner – Trifork – NPI (cat=Spring Boot)
announcement - icon

Navigating the complexities of Spring can be difficult, even for seasoned developers.

If you need direct, practical help and guidance with your own Spring work, Trifork's CTO, Joris Kuipers, is running a closed-door call.

It's free, but it's limited to only 3 seats, so if you need it, I would join quickly and be sure to attend:

>>> CTO Spring Open Office Hour Session - Technical Guidance

With more than 15 years of leading custom software development projects involving Spring, Joris has gained a lot of real-world experience, and this call is about sharing and helping the community.

Enjoy.

Course – LS – All

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

>> CHECK OUT THE COURSE

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.

Course – LS – All

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

>> CHECK OUT THE COURSE
res – REST with Spring (eBook) (everywhere)
2 Comments
Oldest
Newest
Inline Feedbacks
View all comments
Comments are closed on this article!