4

My SpringBoot app makes HTTP requests to external APIs, all of which consume/produce JSON. By default, my app uses Jackson for data-binding, and all HTTP requests (using RestTemplate) have apparently used Accept and Content-Type headers of application/json.

Recently I needed to make use of the Jackson xml data-binding library (not for http data binding), so I included that dependency in the app and it seems that SpringBoot has decided to implicitly use application/xml for all outgoing HTTP requests.

How can I configure the app to default to JSON when making HTTP requests without having to explicitly set it in the header of every request?

user1491636
  • 2,355
  • 11
  • 44
  • 71
  • Did you find a solution for this? I'm having the same problem as well. – Ricardo Pieper Sep 20 '19 at 03:05
  • @RicardoPieper you could try the [interceptor based solution](https://stackoverflow.com/questions/43590448/set-default-content-type-header-of-spring-resttemplate)? Have you tried it? – buræquete Sep 20 '19 at 03:17
  • @RicardoPieper also somewhat personal, but I found [this question](https://stackoverflow.com/questions/57706610/how-to-set-default-messageconverter-to-json-with-jackson-dataformat-xml-added). Identical issue, recently posted, & Brazilian sounding surname =) Is that person a co-worker of yours? – buræquete Sep 20 '19 at 03:24
  • @buræquete This would not work. We have many instantiations of RestTemplate spread throughout many repositories that are loaded during runtime (we do hot-reload), each one of them have some initialization code, which involves essentially writing `new RestTemplate()` at some point. I just wish it would keep choosing to do JSON instead of XML :( And no, I don't know that guy, but I'll look his question to see if anything comes up. He posted it some days ago, and I just discovered I have this issue like 10min ago. – Ricardo Pieper Sep 20 '19 at 03:26
  • @RicardoPieper you can have some XML-based RestTemplates, and some JSON-based ones, and the JSON-based ones' init logic would always include that interceptor add logic. Wouldn't that work? + lol what coincidence then, I was sure that you guys were in same company! I though he was the plebeian dev, and the issue was escalated to the more senior dev after couple weeks – buræquete Sep 20 '19 at 03:28
  • @buræquete I don't understand what you mean...but it does seem that I will have to go through each one of my repositories and change each RestTemplate manually, either adding a content type header or configuring the RestTemplate itself. This is far from ideal... – Ricardo Pieper Sep 20 '19 at 03:33
  • @RicardoPieper repository meaning different apps, modules? If so yeah that would be cumbersome. But all those modules are loaded into the main app, and the xml-dependency is poisoning them all to use XML content type header, is that a correct summary? – buræquete Sep 20 '19 at 03:34
  • @buræquete Yes, exactly. We've been relying on RestTemplate producing JSON, maybe we shouldn't have? – Ricardo Pieper Sep 20 '19 at 03:35
  • @buræquete right now we're going through each project. We figured out a way to know in advance which ones need review. In the end, I guess there is no solution for us. Bounty still open though :p – Ricardo Pieper Sep 20 '19 at 04:09

2 Answers2

2

The answer is mostly focused on @RicardoPieper's case, but the single RestTemplate case (from OP) can still use this intercept based solution

In my opinion, it is best to be explicit, and not depend on the Content-Type setting in any RestTemplate. So set your header values for all calls like;

HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Post> entity = new HttpEntity<>(post, headers);
ResponseEntity<Post> response = someTemplate.postForEntity(uri, entity, Post.class);

But for your case, I came up with an idea, again doing the intercept solution but from a higher perspective (though it has an assumption still)

Have a listener for context refresh event;

@Component
public class MyListener implements ApplicationListener<ContextRefreshedEvent> {

    @Autowired
    private Map<String, RestTemplate> templates;

    public void onApplicationEvent(ContextRefreshedEvent event) {
        templates.entrySet().stream()
                .filter(this::isJsonBased)
                .map(Map.Entry::getValue)
                .forEach(template -> template.setInterceptors(Collections.singletonList(new JsonMimeInterceptor())));
    }

    private boolean isJsonBased(Map.Entry<String, RestTemplate> entry) {
        return entry.getKey().toLowerCase().contains("json");
    }
}

Here I am getting all RestTemplate beans in the context (with their bean names included, using the Map autowire feature), and doing a filter first, though this is the assumption part, I thought having "json" in the name of the JSON focused RestTemplate beans, like;

@Bean
public RestTemplate jsonTemplate() {
    return new RestTemplate();
}

and apply the intercept logic to all those beans.

public class JsonMimeInterceptor implements ClientHttpRequestInterceptor {

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        HttpHeaders headers = request.getHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        return execution.execute(request, body);
    }
}

What do you think? =) It worked on my demo app, though there needs to be some way to distinguish between XML based, and JSON based RestTemplate beans. If you can create such a distinction, you can add that logic in the isJsonBased() method and still use this idea!

buræquete
  • 14,226
  • 4
  • 44
  • 89
  • I'm giving you the bounty for the following reasons: 1 - You invested your time; 2 - Showed that there is no immediate solution for my problem; 3 - perfectly answered OP's question; 4 - Showed effective use of spring events, interceptors and DI, lots of information condensed in a single post that I'll definitely use as a reference. I forgot to mention that those modules do not know Spring exists and aren't even not instantiated by Spring DI... but in part this is a lie, since they use RestTemplate anyway. This whole thing revealed a coupling that we weren't entirely aware of. – Ricardo Pieper Sep 20 '19 at 17:14
1

Simply override and configure the default content type in WebMvcConfigurerAdapter#configureContentNegotiation(ContentNegotiationConfigurer) as shown below:

@EnableWebMvc
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        configurer.defaultContentType(MediaType.APPLICATION_JSON);
    }
}
Sync
  • 3,571
  • 23
  • 30
  • Tried that already and it didn't work. Outgoing requests still seem to be sent with Content-Type `application/xml`. The external APIs are responding back with a 415 code. When I look at the http message converters in the `RestTemplate` object, they are the xml ones from fasterxml instead of the json ones. I assume that I need to configure those as well somehow. – user1491636 Dec 23 '17 at 18:28