How to set default MessageConverter to JSON with jackson-dataformat-xml added?
Asked Answered
S

5

10

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.

Syrinx answered 29/8, 2019 at 8:59 Comment(0)
S
11

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;
}
Syrinx answered 14/10, 2019 at 12:3 Comment(3)
Is it Jackson that decides or is it Spring? This should be directly configurable via spring propertiesMaddiemadding
Holy! Why on earth did they implement it that way?Cavour
Using RestTemplate may be using Spring Boot wrong after all, as the documentation for RestTemplateBuilder hints at. See below for full explanation.Staples
P
3

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.

Preparatory answered 10/10, 2019 at 6:31 Comment(0)
S
1

As Johnny Lim's answer very nicely outlined (backed up with a test), simply using RestTemplateBuilder instead of RestTemplate circumvents this problem.

Well hidden in the Spring Boot documentation it actually hints at this desired default behavior:

The auto-configured RestTemplateBuilder ensures that sensible HttpMessageConverters are applied to RestTemplate instances.

When we encountered this problem in our applications, we solved it by adding a bean like this to our Config:

@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
    return builder.build();
}

and then simply reusing that in all our Services:

@Autowired
private RestTemplate restTemplate;

Of course, one could as well replace all occurences of new RestTemplate() with new RestTemplateBuilder.build() instead as it also provides a convenient fluent API to configure any kind of request.

Staples answered 18/10, 2023 at 11:38 Comment(0)
C
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);
Carney answered 3/3, 2022 at 14:27 Comment(1)
For optimized memory usage, using constants throughout is advisable, i.e. headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).Staples
O
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.

Oca answered 22/3, 2022 at 17:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.