How to post JSON-serialized form data with RestTemplate

RestTemplate is often used for consuming RESTful web services from Spring applications. It’s little known however, how to use it for more advanced use cases, that is, when you need to combine POSTing form data along with a JSON-serialized data. In this guide we’ll explore the key points of being able to do that, I’ll also show you how to unit test your REST client.

I’ve recently needed to develop Java client library for fetching some data from SuiteCRM. Although its documentation said it provided a REST API, after looking at it, it was obvious that the usual way I was accustomed to using RestTemplate wouldn’t work here.

SugarCRM’s PHP example

The method login which you need to call in order to obtain a session ID from SuiteCRM expects to be called according to the following example.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?php
    ...
    $curl_request = curl_init();
    curl_setopt($curl_request, CURLOPT_URL, $url);
    ...
    $parameters = array(
         "user_auth" => array(
              "user_name" => $username,
              "password" => md5($password),
              "version" => "1"
         ),
         "application_name" => "RestTest",
         "name_value_list" => array(),
    );

    $jsonEncodedData = json_encode($parameters);

    $post = array(
         "method" => 'login',
         "input_type" => "JSON",
         "response_type" => "JSON",
         "rest_data" => $jsonEncodedData
    );

    curl_setopt($curl_request, CURLOPT_POSTFIELDS, $post);
    $result = curl_exec($curl_request);
    ...

?>

 

Some parts of the test PHP script was left out for brevity and for focusing on that fact that SuiteCRM’s REST API required a combination of posting form and JSON data. You can get the full working example here.

Going from PHP/cURL to Spring/RestTemplate

Much of the actual implementation has been also omitted here, as I’d like to show you the most important aspects of the solution.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public final class SuiteCRMServiceImpl implements SuiteCRMService {

    private final URI suiteCRMUri;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private RestOperations restTemplate;

    ...

    @Override
    public String login(UserAuth userAuth) {
        LoginRequest loginRequest = LoginRequest.builder().userAuth(userAuth).applicationName(APPLICATION_NAME).build();
        UserEntry userEntry = doPost(loginRequest, UserEntry.class);
        return userEntry.getId();
    }

    protected <R> R doPost(AbstractRequest request, Class<R> responseType) {
        ResponseEntity<R> responseEntity = null;

        try {
            MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
            formData.add("method", request.getMethod());
            formData.add("input_type", "JSON");
            formData.add("response_type", "JSON");

            String restData = objectMapper.writeValueAsString(request);
            formData.add("rest_data", restData);

            RequestEntity requestEntity = RequestEntity.post(suiteCRMUri)
                    .contentType(APPLICATION_FORM_URLENCODED)
                    .body(formData);

            responseEntity = new RestTemplate().postForEntity(suiteCRMUri, requestEntity, responseType);
        } catch (Exception e) {
            throw new SuiteCRMServiceException(e);
        }

        HttpStatus statusCode = responseEntity.getStatusCode();
        if (HttpStatus.OK.equals(statusCode)) {
            return responseEntity.getBody();
        }

        throw new SuiteCRMServiceException(statusCode.getReasonPhrase());
    }

    ...

}

 

In order to be able to POST form data we need to ensure two important points.

  1. The payload of the HTTP request must be a MultiValueMap. If you take a closer look at how FormHttpMessageConverter works, it’s apparent that it was meant to work with MultiValueMaps only.
  2. The content type of the request need to be APPLICATION_FORM_URLENCODED or MULTIPART_FORM_DATA, because FormHttpMessageConverter supports only these media types.

Unit testing

As the whole solution need the presence of that remote service it relies on, it’s impractical if we just leave the actual calls which goes over the wire in automated test cases.

Fortunately the functionality of RestTeample was abstracted out to interface RestOperations. Its sole purpose is to help implementors mock RestTeample out in unit test.

As you can see in the code above, RestTemplate isn’t created for every request, but it’s rather auto wired at, so that it could be also mocked.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import static org.mockito.Matchers.*
import static org.mockito.Mockito.*

class SuiteCRMServiceUnitTest {

    static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()

    RestOperations restTemplate
    SuiteCRMService suiteCRMService

    @Before
    void before() {
        restTemplate = mock(RestOperations)

        when(restTemplate.postForEntity(any(URI), anyObject(), argThat(new ClassOrSubclassMatcher<UserEntry>(UserEntry)))).thenReturn(ResponseEntity.ok(new UserEntry("1234")))
        ...

        suiteCRMService = new SuiteCRMServiceImpl("http://localhost", OBJECT_MAPPER, restTemplate)
    }

    @Test
    void testLogin() {
        ...
    }

    ...

}

 

Conclusion

Spring’s RestTeample is very flexible in providing various way of customizing HTTP requests and processing the response. Hardy is there anything you couldn’t do with it.

Laszlo Csontos
 

I've been coding since the age of 9. I knew from childhood that all I wanted to do was code. Now I've been coding for 25 years, with Java for 18 years and professionally for 13 years. During past projects I worked in various roles as a consultant, developer, mentor, team leader and architect. My focus areas have been database- oriented back-end applications, performance tuning techniques and distributed systems. In the last 3 years, I specialized in building microservices with the Spring Ecosystem and also contributed to some of its sub-projects. The newest venture of mine is the creation of craftingjava.com, which aims at helping young software engineers learn Spring.

Leave a Reply

avatar
  Subscribe  
Notify of