8

I have a working spring boot application that uses JSON as exchange data format. Now I had to add a service that sends their data only in xml. I added jackson-dataformat-xml to my pom and it worked perfectly.

@Service
public class TemplateService {

    private final RestTemplate restTemplate;
    private final String serviceUri;

    public TemplateService (RestTemplate restTemplate, @Value("${service.url_templates}") String serviceUri) {
        this.restTemplate = restTemplate;
        this.serviceUri = serviceUri;
    }

    public boolean createTemplate(Template template) {
        HttpHeaders headers = new HttpHeaders();
        headers.setAccept(Collections.singletonList(MediaType.APPLICATION_XML));
        headers.setContentType(MediaType.APPLICATION_XML);
        HttpEntity entity = new HttpEntity<>(template, headers);
        ResponseEntity<Template> response = restTemplate.exchange(serviceUri, HttpMethod.POST, entity, Template.class);
        if (response.getStatusCode().is2xxSuccessful()) {
            // do some stuff
            return true;
        }
        return false;
    }
}

Now unfortunately after adding the dependency all my other POST methods send XML by default. Or the Content is set to application/xml.But I'd like to have JSON here.

@Service
public class SomeOtherService{

    private final RestTemplate restTemplate;
    private final String anotherUri;

    public SomeOtherService(RestTemplate restTemplate, @Value("${anotherUri.url}") String anotherUri) {
        this.restTemplate = restTemplate;
        this.anotherUri = anotherUri;
    }

    public ComponentEntity doSomething(String projectId, MyNewComponent newComponent) {
        ResponseEntity<MyNewComponent> result = this.restTemplate.exchange(anotherUri ,HttpMethod.POST, new HttpEntity<>(newComponent), MyNewComponent.class);
    //...
    }
}

I did not set the headers explicitly as there are lots of POST requests and I don't want to alter them all. Is there a way to set the default Content to JSON?

So far I tried

  1. adding an interceptor. But sometimes I need to have XML.
  2. Overriding content negotiation
    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        configurer.defaultContentType(MediaType.APPLICATION_JSON);
    }
  1. setting
restTemplate.setMessageConverters(Collections.singletonList(new MappingJackson2HttpMessageConverter()));

and using a new RestTemplate() in the service where I want to have XML.

==> Number 3 actually works, but feels kind of wrong.

I was expecting to set the default Content type somewhere so that JSON is used in normal cases where nothing is set and XML where I explicitly set the Content to XML.

Thanks for any help.

Cliff Pereira
  • 403
  • 4
  • 14

4 Answers4

10

What we ultimately found out, is that the the order of the message converters is highly important. Jackson seems to place the XML message converter before the JSON message converter. So we moved the XML message convert to the end and it worked.

    @Bean
    RestTemplate restTemplate() {
    RestTemplate restTemplate = new RestTemplate();
    // move XML converter to the end of list
    List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
    for (int i = 0; i < messageConverters.size() -1 ; i++) {
        if (messageConverters.get(i) instanceof MappingJackson2XmlHttpMessageConverter) {
            Collections.swap(messageConverters, i,messageConverters.size() - 1);
        }
    }

    restTemplate.setMessageConverters(messageConverters);

    // add interceptors if necessary
    restTemplate.setInterceptors(Collections.singletonList(catalogInterceptior()));
    return restTemplate;
}
Cliff Pereira
  • 403
  • 4
  • 14
1

Creating a RestTemplate bean from the auto-configured RestTemplateBuilder bean and using it doesn't manifest the situation. This is a test to demonstrate the situation.

Johnny Lim
  • 5,623
  • 8
  • 38
  • 53
0

You could change the order of message converters as suggested by the accepted answer and that works perfectly fine. Jackson picks up the message converter with the highest priority, if there is no Content-Type header present for the request.

So if you don't want to change the config of RestTemplate, you could set the Content-Type to application/json and that will instruct Jackson to use the correct message converter on your requests:

HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.ACCEPT, "application/json");
headers.add(HttpHeaders.CONTENT_TYPE, "application/json");

var httpEntity = new HttpEntity<>(requestBody, headers);
ResponseEntity<String> responseEntity = restTemplate.exchange(
    tenantConfig.getTokenUri(), HttpMethod.POST, httpEntity, String.class);
Nima Ajdari
  • 2,754
  • 2
  • 14
  • 18
0

The order is controlled by Spring. If you look at the default constructor for RestTemplate, the MessageConverters are added to the list based on the booleans determined from the static block directly above (there is one Spring property that is consulted--spring.xml.ignore--and that controls whether or not to exclude loading all the XML-related infrastructure). The order of the list does not seem modify-able during instatiation. This does seem like a limitation although there is a constructor that takes in a list of MessageConverters and uses that instead.

Dharman
  • 30,962
  • 25
  • 85
  • 135