2

I've been developing a cloud app to mess a little with Spring Cloud and such. Now I'm stuck trying to send a POST or a PUT request to a Spring Data Rest backend using the RestTemplate API but everything I tried ends with an error: HttpMessageNotReadableException: Cannot deserialize instance of java.lang.String out of START_OBJECT token, HttpMessageNotReadableException: Could not read document: Can not deserialize instance of java.lang.String out of START_ARRAY token, ...from request with content type of application/xml;charset=UTF-8!, Error 400 null... you name it. After researching I discovered that it actually is quite hard to consume HAL JSON with RestTemplate (level 3 JSON hypermedia if I recall correctly) but I want to know if it is possible.

I'd like to see some working (detailed if possible) examples of a RestTemplate sending POST and PUT to a Spring Data Rest backend.

Edit: I tried postForEntity, postForLocation, exchange and it just ended in different kinds of errors. Those are some snippets I tried (there're more, it's just that I dispose them).

My entity:

@Entity
public class Account implements Serializable {

private static final long serialVersionUID = 1L;

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String name;

@NotNull
@NotEmpty
private String username;

@NotNull
@NotEmpty
private String authorities;

@NotNull
@NotEmpty
private String password;

//Constructor, getter and setter

Some restTemplate attemps:

    public Account create(Account account) {
    //Doesnt work :S
    MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
    map.add("name", account.getName());
    map.add("username", account.getUsername());
    map.add("password", account.getPassword());
    map.add("authorities", account.getAuthorities());

    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON);
    final HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<MultiValueMap<String, String>>(map,
            headers);

    return restTemplate.exchange(serviceUrl + "/accounts", HttpMethod.POST, entity, Account.class).getBody();
}

//Also tried with a AccountResource which extends from ResourceSupport and doesn't work either. This one gives me a error saying it cannot deserialize Account["name"].

Also tried like this and got an error about header being application/xml: RestTemplate POSTing entity with associations to Spring Data REST server

The other ones just repeat one of those errors.

Community
  • 1
  • 1
Manuel Páez
  • 121
  • 1
  • 4
  • 13

1 Answers1

6

You need to configure your RestTemplate so it can consume the application/hal+json content type.

It has already been addressed in some other posts, such as this one or that one, and on a bunch of blog posts, such as here. The following solution works for a Spring Boot project:

First, configure your RestTemplate using a bean:

// other import directives omitted for the sake of brevity
import static org.springframework.hateoas.MediaTypes.HAL_JSON;

@Configuration
public class RestTemplateConfiguration {

    @Autowired
    private ObjectMapper objectMapper;

    /**
     *
     * @return a {@link RestTemplate} with a HAL converter
     */
    @Bean
    public RestTemplate restTemplate() {

        // converter
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        converter.setSupportedMediaTypes(Arrays.asList(HAL_JSON));
        converter.setObjectMapper(objectMapper);

        RestTemplate restTemplate = new RestTemplate(Collections.singletonList(converter));

        return restTemplate;
    }
}

Then, let Spring inject the RestTemplate where you need to consume the REST backend, and use one of the many variants of RestTemplate#exchange:

@Autowired
public RestTemplate restTemplate;

...
// for a single ressource

// GET
Account newAccount = restTemplate.getForObject(url, Account.class);

// POST
Account newAccount = restTemplate.exchange(serviceUrl + "/accounts", HttpMethod.POST, entity, Account.class).getBody();
// or any of the specialized POST methods...
Account newAccount = restTemplate.postForObject(serviceUrl + "/accounts", entity, Account.class);

For a collection, you will manipulate a PagedResources

// for a collection
ParameterizedTypeReference<PagedResources<Account>> responseType =
        new ParameterizedTypeReference<PagedResources<Account>>() {};

// GET
PagedResources<Account> accounts =
        restTemplate.exchange(url, HttpMethod.GET, null, responseType).getBody();

//
okonomichiyaki
  • 8,355
  • 39
  • 51
Marc Tarin
  • 3,109
  • 17
  • 49
  • I recall that blog but I didn't implement the code because I don't know where that "HAL_JSON" comes from. I tried using MediaType.parseMediaType("application/hal+json") instead of that HAL_JSON and doesn't work. Error is the same: Can not deserialize instance of java.lang.String out of START_ARRAY token – Manuel Páez Feb 21 '17 at 19:42
  • This is the exact track: Could not read document: Can not deserialize instance of java.lang.String out of START_ARRAY token at [Source: org.apache.catalina.connector.CoyoteInputStream@278f6875; line: 1, column: 9] (through reference chain: com.example.core.webservicesrepositories.accounts.entities.Account["name"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.lang.String out of START_ARRAY token – Manuel Páez Feb 21 '17 at 20:19
  • I managed to solve it by replacing that POST using exchange with a postForEntity. I'll check your answer as the one since you guided me way too much into solving this. Thank you very much! I had the GET as you suggested before I made this question and it worked even without that tune to RestTemplate, do you have any idea why? – Manuel Páez Feb 21 '17 at 23:49
  • @ManuelPáez Sorry, I usually omit import directives to save up some space, but sometimes static import are a bit painful to identify. I edited my post for the sake of clarity. – Marc Tarin Feb 22 '17 at 09:53
  • Thinking about it, postForObject might be even more suitable in this case if you don't care about the response header and status (it saves you from calling getBody()). – Marc Tarin Feb 22 '17 at 10:03
  • This approach is no more working with SpringBoot 2.0. See https://stackoverflow.com/questions/43991995/testing-spring-data-rest-endpoints-using-resttemplate-not-working-in-springboot – K. Siva Prasad Reddy Jun 04 '17 at 11:40