Skip to content

RestTemplate converts to XML rather to JSON by default  #27056

@oridool

Description

@oridool

I've upgraded from SpringBoot 2.4.6 to 2.5.1, and noticed the following bug / regression issue regarding RestTemplate.
All my tests started to fail because the framework creates XML body instead of JSON.

In 2.4.6, I don't specify any ContentType for each request.
When I send an HTTP request, the framework converts my object to JSON body.
Here is a sample code:

        HttpHeaders headers = addBearerAuthHeader(username); // sets AUTHORIZATION header
        ResponseEntity<AccountResource> response = restTemplate.exchange("/sets/" + setId + "/accounts", POST,
                new HttpEntity<>(accountDTO, headers), AccountResource.class);

Body is created as standard JSON:

{"name":"tfWqyuUgGZ","basePlatformId":"platform-5466","description":null,"targetId":70310,"setId":"109c05c8-0be0-4e32-8122-cbfd1f1f8785","properties":{"address":"129.83.21.137","username":"DInuyP","policyId":56647,"secret":"5dSlX7Rn"}}

After upgrading to 2.5.X, the framework started creating XML instead of JSON:

<AccountDTO><name>I4rDnGaJkL</name><basePlatformId>platform-9615</basePlatformId><description/><targetId>88192</targetId><setId>109c05c8-0be0-4e32-8122-cbfd1f1f8785</setId><properties><address>251.172.20.153</address><username>TFWSBI</username><policyId>93423</policyId><secret>IB6WlQqq</secret></properties></AccountDTO>

To work around this issue, I can add the following line to the code above:

headers.setContentType(MediaType.APPLICATION_JSON);

However, this is not a very good solution, since now I have to change a lot of places in my code.
Unfortunately, changing a single place by using an HTTP interceptor (ClientHttpRequestInterceptor) to set the header isn't possible, because Object to Body conversion occurs before interceptor is called.

Another workaround I found after digging the code, is utilizing system property
-Dspring.xml.ignore=true.
However, this is not perfect in case the code is handling both JSON and XML data.

I've started debugging the issue and noticed the difference between the SpringBoot versions and how this can be explained.
The conversion starts in:

for (HttpMessageConverter<?> messageConverter : getMessageConverters()) {

In SpringBoot 2.4.6, when printing getMessageConverters().toStoring(), I get:
[
org.springframework.http.converter.ByteArrayHttpMessageConverter@6e78fcf5,
org.springframework.http.converter.StringHttpMessageConverter@56febdc,
org.springframework.http.converter.ResourceHttpMessageConverter@3b8ee898,
org.springframework.http.converter.xml.SourceHttpMessageConverter@7d151a,
org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter@294bdeb4,
org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter@5300f14a,
org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@1f86099a,
org.springframework.http.converter.smile.MappingJackson2SmileHttpMessageConverter@77bb0ab5,
org.springframework.http.converter.cbor.MappingJackson2CborHttpMessageConverter@f2c488
]

However, in the same code running in 2.5.X, notice the difference:
[
org.springframework.http.converter.ByteArrayHttpMessageConverter@452c8a40,
org.springframework.http.converter.StringHttpMessageConverter@534243e4,
org.springframework.http.converter.ResourceHttpMessageConverter@29006752,
org.springframework.http.converter.xml.SourceHttpMessageConverter@470a9030,
org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter@66d57c1b,
org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter@27494e46,
org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@d59970a,
org.springframework.http.converter.smile.MappingJackson2SmileHttpMessageConverter@1e411d81,
org.springframework.http.converter.cbor.MappingJackson2CborHttpMessageConverter@53b98ff6
]

The reason for the difference in converters is this line:
jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);

jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);

In my sample, requestContentType = null in runtime, since I don't initialize any specific contentType.
Therefore, the first converter that matches the conditions is MappingJackson2XmlHttpMessageConverter.

I don't know why there is a difference between the class loaders and why jackson2XmlPresent=true on 2.5.X while it is false on 2.4.X.
I haven't changed anything in my code or in the project dependencies except the SpringBoot version.

Maybe there is a workaround, but IMHO it is still a breaking change between 2.5.X to 2.4.X.

Thanks,
Ori.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions