How to post JSON-serialized form data with RestTemplate

March 31, 2017 · 4 min read – Last updated on December 08, 2018

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.

<?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.

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 MultiValueMap's 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, RestTeample isn’t created for every request, but it’s rather auto wired at, so that it could be also mocked.

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.