88

I want to update the SerializationConfig.Feature... properties of the jackson mapper used by Spring RestTemplate, Any idea how I can get to it or where I can/should configure it.

Usman Ismail
  • 17,999
  • 14
  • 83
  • 165

5 Answers5

106

The default RestTemplate constructor registers a set of HttpMessageConverters:

this.messageConverters.add(new ByteArrayHttpMessageConverter());
this.messageConverters.add(new StringHttpMessageConverter());
this.messageConverters.add(new ResourceHttpMessageConverter());
this.messageConverters.add(new SourceHttpMessageConverter());
this.messageConverters.add(new XmlAwareFormHttpMessageConverter());
if (jaxb2Present) {
    this.messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
}
if (jacksonPresent) {
    this.messageConverters.add(new MappingJacksonHttpMessageConverter());
}
if (romePresent) {
    this.messageConverters.add(new AtomFeedHttpMessageConverter());
    this.messageConverters.add(new RssChannelHttpMessageConverter());
}

The MappingJacksonHttpMessageConverter in turns, creates ObjectMapper instance directly. You can either find this converter and replace ObjectMapper or register a new one before it. This should work:

@Bean
public RestOperations restOperations() {
    RestTemplate rest = new RestTemplate();
    //this is crucial!
    rest.getMessageConverters().add(0, mappingJacksonHttpMessageConverter());
    return rest;
}

@Bean
public MappingJacksonHttpMessageConverter mappingJacksonHttpMessageConverter() {
    MappingJacksonHttpMessageConverter converter = new MappingJacksonHttpMessageConverter();
    converter.setObjectMapper(myObjectMapper());
    return converter;
}

@Bean
public ObjectMapper myObjectMapper() {
    //your custom ObjectMapper here
}

In XML it is something along these lines:

<bean id="restOperations" class="org.springframework.web.client.RestTemplate">
    <property name="messageConverters">
        <util:list>
            <bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"/>
            <bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
            <bean class="org.springframework.http.converter.ResourceHttpMessageConverter"/>
            <bean class="org.springframework.http.converter.xml.SourceHttpMessageConverter"/>
            <bean class="org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter"/>
            <bean class="org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter"/>
            <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
                <property name="objectMapper" ref="customObjectMapper"/>
            </bean>
        </util:list>
    </property>
</bean>

<bean id="customObjectMapper" class="org.codehaus.jackson.map.ObjectMapper"/>

Note that the transition isn't really 1:1 - I have to explicitly create messageConverters list in XML while with @Configuration approach I could reference existing one and simply modify it. But this should work.

Tomasz Nurkiewicz
  • 334,321
  • 69
  • 703
  • 674
  • i would be grateful if you could translate this to XML. really want to try this but not sure how to configure it. many thanks! – Justin Feb 24 '12 at 20:24
  • 11
    Not sure if this is just due to a version difference, but I believe the correct name for the Jackson converter class is MappingJackson*2*HttpMessageConverter – nbrooks Sep 17 '15 at 22:05
  • 3
    I would probably avoid adding a new mappingJacksonHttpMessageConverter in the beginning of the existing default ones, but replace the existing Jackson message converter instead. Otherwise you end up having 2 Jackson message converters in the list. – mvogiatzis Feb 06 '16 at 16:34
  • I tried adding it at index 0 and I got some very strange JSON parse exceptions. Removing the existing worked better like @mvogiatzis suggested – Mads Hoel Dec 15 '17 at 12:54
  • I was need to change `RestOperations -> RestTemplate` in `@Bean` definition to make this works – chill appreciator Jun 24 '20 at 20:32
  • Shouldn't myObjectMapper() and mappingJacksonHttpMessageConverter()) be called by requesting a parameter in the @Bean methods? – borjab Dec 20 '21 at 17:31
34

If you are not using Spring IOC, you can do something like this (Java 8):

ObjectMapper objectMapper = new ObjectMapper();
// configure your ObjectMapper here

RestTemplate restTemplate = new RestTemplate();    

MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
messageConverter.setPrettyPrint(false);
messageConverter.setObjectMapper(objectMapper);
restTemplate.getMessageConverters().removeIf(m -> m.getClass().getName().equals(MappingJackson2HttpMessageConverter.class.getName()));
restTemplate.getMessageConverters().add(messageConverter);
Matt Sidesinger
  • 2,124
  • 1
  • 22
  • 18
15

RestTemplate will initialize its default message converters. You should replace the MappingJackson2HttpMessageConverter with your own bean, which should use the object mapper you want to use. This worked for me:

@Bean
public RestTemplate restTemplate() {
    final RestTemplate restTemplate = new RestTemplate();

    //find and replace Jackson message converter with our own
    for (int i = 0; i < restTemplate.getMessageConverters().size(); i++) {
        final HttpMessageConverter<?> httpMessageConverter = restTemplate.getMessageConverters().get(i);
        if (httpMessageConverter instanceof MappingJackson2HttpMessageConverter){
            restTemplate.getMessageConverters().set(i, mappingJackson2HttpMessageConverter());
        }
    }

    return restTemplate;
}

@Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
    MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
    converter.setObjectMapper(myObjectMapper());
    return converter;
}

@Bean
public ObjectMapper myObjectMapper() {
    // return your own object mapper
}
mvogiatzis
  • 612
  • 7
  • 10
  • It seems it's important to name the method something like `myObjectMapper()` but not just `objectMapper()`. For some reason it seems the method is never called and you get the default `ObjectMapper`. – herman Jun 15 '16 at 14:45
  • 1
    This worked, but seems there is a typo in the answer? `restTemplate.getMessageConverters().set(i, mappingJackson2HttpMessageConverter);` should be `restTemplate.getMessageConverters().set(i, mappingJackson2HttpMessageConverter());` (note the parenthesis in mappingJackson2HttpMessageConverter(), making it a method call instead of a reference) – Jens Wegar Jul 12 '18 at 06:49
  • True, or you can alternatively add the `mappingJackson2HttpMessageConverter` as a parameter to the `restTemplate` function use it as is. – mvogiatzis Jul 27 '18 at 10:26
  • 1
    you dont have to create a new one, you can just set your mapper if (httpMessageConverter instanceof MappingJackson2HttpMessageConverter){ ((MappingJackson2HttpMessageConverter)httpMessageConverter).setObjectMapper(mapper()); } – Kalpesh Soni Mar 27 '19 at 15:30
  • In Java8 you can use streams or Java14 you can use pattern matching for instanceof. Otherwise nice declarative solution. – Andrej Buday Mar 25 '20 at 16:32
11

To complete the other answers: if your ObjectMapper just registers a Jackson Module with custom serializers/deserializers, you might want to register your module directly on the existing ObjectMapper from RestTemplate's default MappingJackson2HttpMessageConverter as follows (example without DI but the same applies if using DI):

    SimpleModule module = new SimpleModule();
    module.addSerializer(...);
    module.addDeserializer(...);

    MappingJackson2HttpMessageConverter messageConverter = restTemplate.getMessageConverters().stream()
                    .filter(MappingJackson2HttpMessageConverter.class::isInstance)
                    .map(MappingJackson2HttpMessageConverter.class::cast)
                    .findFirst().orElseThrow( () -> new RuntimeException("MappingJackson2HttpMessageConverter not found"));
    messageConverter.getObjectMapper().registerModule(module);

This will allow you to complete the configuration of the original ObjectMapper (as done by Spring's Jackson2ObjectMapperBuilder), instead of replacing it.

Olivier Gérardin
  • 1,113
  • 14
  • 27
9

Using Spring Boot, it is as simple as:

RestTemplate template = new RestTemplateBuilder()
                            .additionalMessageConverters(new MappingJackson2HttpMessageConverter(objectMapper))
                            .build()

(Tested with Spring Boot 2.2.1)

Wim Deblauwe
  • 25,113
  • 20
  • 133
  • 211
  • With this approach your `RestTemplate` will have only one `MessageConverter`: `MappingJackson2HttpMessageConverter`. But I still like it – chill appreciator Jun 24 '20 at 20:55
  • `additionalMessageConverters` is not really additive. It will perform a full replacement and you'll loose other converters. It's stated on its docstring. (It adds to the builder's list of converters, which will fully replace the instance's) – Ming Aug 21 '20 at 19:23
  • @Ming To be more precise, `additionalMessageConverters` will not replace already configured converters on the **builder**. "Any converters configured on the builder will replace RestTemplate's default converters" means that the `RestTemplate` instance produced by `RestTemplateBuilder.build()` method will not have any other converters except those configured on the builder. So, as soon as you configure **any** `HttpMessageConverter` on the builder instance, they will replace default `RestTemplate` conveters. This could be circumvented by calling `builder.defaultMessageConverters()` first. – Ruslan Stelmachenko Jun 20 '23 at 12:53
  • Also, Spring Boot has already configured `RestTemplateBuilder` bean in `prototype` scope. This bean is already configured with default converters (the list includes all `HttpMessageConverter`s from `HttpMessageConverters` bean, that are even better than `RestTemplate`'s default). Also the bean has all `RestTemplateCustomizer` beans applied. So, you can just inject `RestTemplateBuilder` into your component and then `additionalMessageConverters` would really **add** converters, because at the moment of the calling the list would not be empty. – Ruslan Stelmachenko Jun 20 '23 at 13:01