10

I'm having some issues autowiring the default Jackson XmlMapper in one of my Spring Boot projects. I've created a simple example project that illustrates this.

What I'm doing here is roughly based on this:

http://docs.spring.io/spring-boot/docs/1.2.2.RELEASE/reference/htmlsingle/#howto-write-an-xml-rest-service

http://docs.spring.io/spring-boot/docs/1.2.2.RELEASE/reference/htmlsingle/#howto-customize-the-jackson-objectmapper

From pom.xml

<!-- ... -->

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.2.2.RELEASE</version>
</parent>

<!-- ... -->

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.dataformat</groupId>
        <artifactId>jackson-dataformat-xml</artifactId>
    </dependency>
</dependencies>

<!-- ... -->

The main class:

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

The Demo POJO, not specifying @XmlRootElement, so it won't use JAXB:

@JsonInclude(Include.NON_NULL)
public class Demo {
    private String stringProperty;
    private int intProperty;
    public String getStringProperty() {
        return stringProperty;
    }
    public void setStringProperty(String stringProperty) {
        this.stringProperty = stringProperty;
    }
    public int getIntProperty() {
        return intProperty;
    }
    public void setIntProperty(int intProperty) {
        this.intProperty = intProperty;
    }
}

The Demo Controller:

@RestController
public class DemoController {
    @Autowired 
    private ObjectMapper objectMapper;
    // @Autowired 
    // private XmlMapper xmlMapper;

    @RequestMapping(value = "/demo", method = RequestMethod.GET)
    public Demo getDemo() {
        Demo demo = new Demo();
        demo.setStringProperty("Hello world!");
        demo.setIntProperty(42);
        return demo;
    }
}

Everything works fine the way it is, depending on Accept headers, either JSON or XML will be returned.

I can easily autowire the default ObjectMapper configured by Spring Boot. So far so good.

If I comment in the autowiring of the XmlMapper I get:

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.fasterxml.jackson.dataformat.xml.XmlMapper] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:1301)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1047)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:942)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:533)
... 18 more

Any idea why this is? I would have assumed it to work the same way as the ObjectMapper. Just to clarify, I don't want to customise the mappers, I simply want a reference to the default ones created by Spring Boot.

ci_
  • 8,594
  • 10
  • 39
  • 63
  • Probably Spring Boot not contains XmlMapper in IoC container, try to inject bean first. – kris14an Mar 02 '15 at 14:33
  • I know I can create my own XmlMapper, but I want the default one, the one that is actually being used for mapping a class to Xml in the response body, which is working correctly right now, so there must be an XmlMapper somewhere right? @kris14an – ci_ Mar 02 '15 at 14:47
  • I've solved the problem much simpler. I simply don't map the XmlMapper. I just construct it in the method block. Possibly it won't work if the xml to process is too complex. – peterh Feb 07 '22 at 16:33

3 Answers3

25

Based on the answer by @Andy Wilkinson I did some more digging and found a way to get at what I wanted. Spring Boot does not expose the XmlMapper as a Bean but it does expose the message converter it is used in, which is MappingJackson2XmlHttpMessageConverter. So I could auto-wire that bean and get the ObjectMapper (which is now an XmlMapper) from it.

The Demo Controller from my question now looks like this:

    @RestController
    public class DemoController {
        @Autowired 
        private ObjectMapper objectMapper;
        @Autowired
        private MappingJackson2XmlHttpMessageConverter xmlConverter;

        @RequestMapping(value = "/demo", method = RequestMethod.GET)
        public Demo getDemo() {
            Demo demo = new Demo();
            demo.setStringProperty("Hello world!");
            demo.setIntProperty(42);
            ObjectMapper xmlMapper = xmlConverter.getObjectMapper();
            return demo;
        }
    }
Bastian Stein
  • 2,147
  • 1
  • 13
  • 28
ci_
  • 8,594
  • 10
  • 39
  • 63
  • 1
    I'm trying to do this exact same thing with the latest Spring-Boot, but finding that I cannot Autowire the ObjectMapper or the MappingJackson2XmlHttpMessageConverter. Both result in "No qualifying bean of type" errors. – robross0606 Mar 05 '15 at 18:36
  • 1
    @robross0606 Check that you have the dependencies in the pom.xml in the question. – ci_ Mar 05 '15 at 20:04
  • Already checked that. I'm using Gradle for dependency management, but have confirmed that I have all the same dependencies as listed above in your Maven POM. – robross0606 Mar 05 '15 at 20:06
  • @robross0606 Not sure then, I created that minimal project specifically for this question, there is no more too it. I'm not familiar with gradle. Maybe a new question? – ci_ Mar 05 '15 at 20:09
14

Spring Boot does not expose the XmlMapper as a bean as it would conflict with the ObjectMapper bean that's used for JSON mapping (Jackson's XmlMapper is a subclass of Jackson's ObjectMapper).

Andy Wilkinson
  • 108,729
  • 24
  • 257
  • 242
  • 13
    This answers the question of course. An implied next question would have been, "can I get at the XmlMapper some other way?", and I've found that you can autowire `MappingJackson2XmlHttpMessageConverter` instead and get the `XmlMapper` via `.getObjectMapper()`. – ci_ Mar 02 '15 at 15:57
  • 2
    I'm trying to do this exact same thing with the latest Spring-Boot, but finding that I cannot Autowire the ObjectMapper or the MappingJackson2XmlHttpMessageConverter. Both result in "No qualifying bean of type" errors. – robross0606 Mar 05 '15 at 18:35
  • My IDE told me I had that same problem ("No qualifying bean of type"), but when running the code all is fine... – Hans Wouters Aug 26 '22 at 09:49
4

There are two ways of doing so:

@Bean
public XmlMapper xmlMapper(MappingJackson2XmlHttpMessageConverter converter) {
    return (XmlMapper) converter.getObjectMapper();
}

And

@Bean
@Primary
public ObjectMapper objectMapper(MappingJackson2XmlHttpMessageConverter converter) {
    return converter.getObjectMapper();
}

If you want support for JSON and XML then you should register those beans by yourself with BeanFactoryPostProcessor method described in How to create multiple beans of same type according to configuration in Spring?

Adam Ostrožlík
  • 1,256
  • 1
  • 10
  • 16