Close

Spring MVC - Convert XML Messages to Java Objects using @RequestBody and @ResponseBody

[Last Updated: Jun 29, 2026]

Spring MVC supports converting HTTP request bodies (with a Content-Type: application/xml header) to Java objects, and vice versa, using the HttpMessageConverter strategy. However, XML support is not available by default — it is always conditional on a suitable library being present on the classpath.

Historically, Jaxb2RootElementHttpMessageConverter was registered automatically when JAXB was available, which was the case when it was bundled with the JDK (prior to Java 9's deprecation and Java 11's removal of it). Since JAXB was removed from the JDK, it must be added as an external dependency to continue using this converter. Note that if you are already using Spring's Marshaller/Unmarshaller abstractions from spring-oxm, the MarshallingHttpMessageConverter is the recommended alternative.

The modern approach is JacksonXmlHttpMessageConverter (the replacement for the now-deprecated MappingJackson2XmlHttpMessageConverter as of Spring 7.0). Spring will register it automatically, but only if the jackson-dataformat-xml library is explicitly added as a dependency. Once that dependency is on the classpath, you can use @RequestBody and @ResponseBody on your controller methods to convert XML to objects and back, with no additional Spring configuration required.


Example


Maven jackson-dataformat-xml dependency

pom.xml

<dependency>
   <groupId>com.fasterxml.jackson.dataformat</groupId>
   <artifactId>jackson-dataformat-xml</artifactId>
   <version>2.21.2</version>
</dependency>

Creating Backing Objects

package com.logicbig.example;

import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import java.io.Serializable;
// use JAXB's @XmlRootElement for pre spring 6
@JacksonXmlRootElement
public class User implements Serializable {
    private Long id;
    private String name;
    private String password;
    private String emailAddress;
    .............
}

Note that we used Jackson annotation @JacksonXmlRootElement which allows specifying XML element to use for wrapping the root element (default uses 'simple name' of the value class). See more anotations here.
If you are using JAXB (pre Spring 6+) then you will be using JAXB bind annotations for message conversion to work.

Our controller's GET handler (see next) is going to return list of User. it is required to wrap the collection in a separate class to handle generic properly.

package com.logicbig.example;

import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import java.util.List;

@JacksonXmlRootElement(localName = "userList")
public class UserList {
    @JacksonXmlElementWrapper(useWrapping = false)
    private List<User> users;
    .............
}

The Controller

package com.logicbig.example;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

@Controller
@RequestMapping("users")
public class UserController {

    @Autowired
    private UserService userService;

    @RequestMapping(value = "register",
                        method = RequestMethod.POST,
                        consumes = MediaType.APPLICATION_XML_VALUE)
    @ResponseStatus(HttpStatus.CREATED)
    public void handleXMLPostRequest (@RequestBody User user) {
        System.out.println("saving user: " + user);
        userService.saveUser(user);
    }

    @RequestMapping(method = RequestMethod.GET,
                        produces = MediaType.APPLICATION_XML_VALUE)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public UserList getAllUserRequest () {
        UserList list = new UserList();
        list.setUsers(userService.getAllUsers());
        return list;
    }
}

Integration Test

package com.logicbig.example;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.assertj.MockMvcTester;
import org.springframework.web.context.WebApplicationContext;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;

@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration(classes = MyWebConfig.class)
public class RegistrationControllerTest {

    @Autowired
    private WebApplicationContext wac;

    @Autowired
    private UserService userService;

    private MockMvcTester mockMvc;

    @BeforeEach
    public void setup() {
        this.mockMvc = MockMvcTester.from(this.wac);
    }

    @Test
    public void testUserController() {

        assertThat(this.mockMvc.post()
                               .uri("/users/register")
                               .contentType(MediaType.APPLICATION_XML)
                               .content(createUserInXml("joe",
                                                        "joe@example.com",
                                                        "abc")))
                .hasStatus(201);

        assertThat(this.mockMvc.post()
                               .uri("/users/register")
                               .contentType(MediaType.APPLICATION_XML)
                               .content(createUserInXml("mike",
                                                        "mike@example.com",
                                                        "123")))
                .hasStatus(201);

        List<User> savedUsers = userService.getAllUsers();
        assertThat(savedUsers).hasSize(2);
        assertThat(savedUsers.get(0).getName()).isEqualTo("joe");
        assertThat(savedUsers.get(1).getName()).isEqualTo("mike");

        assertThat(this.mockMvc.get()
                               .uri("/users")
                               .accept(MediaType.APPLICATION_XML))
                .hasStatusOk()
                .bodyText()
                .contains("<name>joe</name>")
                .contains("<emailAddress>joe@example.com</emailAddress>")
                .contains("<name>mike</name>")
                .contains("<emailAddress>mike@example.com</emailAddress>");
    }

    private static String createUserInXml(String name,
                                          String email,
                                          String password) {
        return "<user>" +
                "<name>" + name + "</name>" +
                "<emailAddress>" + email + "</emailAddress>" +
                "<password>" + password + "</password>" +
                "</user>";
    }
}
mvn clean test -Dtest="RegistrationControllerTest"

Output

$ mvn clean test -Dtest="RegistrationControllerTest"
[INFO] Scanning for projects...
[WARNING]
[WARNING] Some problems were encountered while building the effective model for com.logicbig.example:spring-xml-example:war:1.0-SNAPSHOT
[WARNING] 'build.plugins.plugin.version' for org.apache.maven.plugins:maven-war-plugin is missing. @ line 50, column 21
[WARNING]
[WARNING] It is highly recommended to fix these problems because they threaten the stability of your build.
[WARNING]
[WARNING] For this reason, future Maven versions might no longer support building such malformed projects.
[WARNING]
[INFO]
[INFO] --------------< com.logicbig.example:spring-xml-example >---------------
[INFO] Building spring-xml-example 1.0-SNAPSHOT
[INFO] from pom.xml
[INFO] --------------------------------[ war ]---------------------------------
[INFO]
[INFO] --- clean:3.2.0:clean (default-clean) @ spring-xml-example ---
[INFO] Deleting D:\example-projects\spring-mvc\spring-xml-example\target
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ spring-xml-example ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory D:\example-projects\spring-mvc\spring-xml-example\src\main\resources
[INFO]
[INFO] --- compiler:3.5.1:compile (default-compile) @ spring-xml-example ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 6 source files to D:\example-projects\spring-mvc\spring-xml-example\target\classes
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ spring-xml-example ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory D:\example-projects\spring-mvc\spring-xml-example\src\test\resources
[INFO]
[INFO] --- compiler:3.5.1:testCompile (default-testCompile) @ spring-xml-example ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 2 source files to D:\example-projects\spring-mvc\spring-xml-example\target\test-classes
[INFO]
[INFO] --- surefire:3.2.5:test (default-test) @ spring-xml-example ---
[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
[WARNING] file.encoding cannot be set as system property, use <argLine>-Dfile.encoding=...</argLine> instead
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.logicbig.example.RegistrationControllerTest
saving user: User{id=null, name='joe', password='abc', emailAddress='joe@example.com'}
saving user: User{id=null, name='mike', password='123', emailAddress='mike@example.com'}
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.946 s -- in com.logicbig.example.RegistrationControllerTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4.205 s
[INFO] Finished at: 2026-06-28T22:28:05+08:00
[INFO] ------------------------------------------------------------------------
INFO: Completed initialization in 2 ms

Example Project

Dependencies and Technologies Used:

  • spring-webmvc 7.0.6 (Spring Web MVC)
     Version Compatibility: 3.2.9.RELEASE - 7.0.6Version List
    ×

    Version compatibilities of spring-webmvc with this example:

      javax.servlet-api:3.x
    • 3.2.9.RELEASE
    • 3.2.10.RELEASE
    • 3.2.11.RELEASE
    • 3.2.12.RELEASE
    • 3.2.13.RELEASE
    • 3.2.14.RELEASE
    • 3.2.15.RELEASE
    • 3.2.16.RELEASE
    • 3.2.17.RELEASE
    • 3.2.18.RELEASE
    • 4.0.0.RELEASE
    • 4.0.1.RELEASE
    • 4.0.2.RELEASE
    • 4.0.3.RELEASE
    • 4.0.4.RELEASE
    • 4.0.5.RELEASE
    • 4.0.6.RELEASE
    • 4.0.7.RELEASE
    • 4.0.8.RELEASE
    • 4.0.9.RELEASE
    • 4.1.0.RELEASE
    • 4.1.1.RELEASE
    • 4.1.2.RELEASE
    • 4.1.3.RELEASE
    • 4.1.4.RELEASE
    • 4.1.5.RELEASE
    • 4.1.6.RELEASE
    • 4.1.7.RELEASE
    • 4.1.8.RELEASE
    • 4.1.9.RELEASE
    • 4.2.0.RELEASE
    • 4.2.1.RELEASE
    • 4.2.2.RELEASE
    • 4.2.3.RELEASE
    • 4.2.4.RELEASE
    • 4.2.5.RELEASE
    • 4.2.6.RELEASE
    • 4.2.7.RELEASE
    • 4.2.8.RELEASE
    • 4.2.9.RELEASE
    • 4.3.0.RELEASE
    • 4.3.1.RELEASE
    • 4.3.2.RELEASE
    • 4.3.3.RELEASE
    • 4.3.4.RELEASE
    • 4.3.5.RELEASE
    • 4.3.6.RELEASE
    • 4.3.7.RELEASE
    • 4.3.8.RELEASE
    • 4.3.9.RELEASE
    • 4.3.10.RELEASE
    • 4.3.11.RELEASE
    • 4.3.12.RELEASE
    • 4.3.13.RELEASE
    • 4.3.14.RELEASE
    • 4.3.15.RELEASE
    • 4.3.16.RELEASE
    • 4.3.17.RELEASE
    • 4.3.18.RELEASE
    • 4.3.19.RELEASE
    • 4.3.20.RELEASE
    • 4.3.21.RELEASE
    • 4.3.22.RELEASE
    • 4.3.23.RELEASE
    • 4.3.24.RELEASE
    • 4.3.25.RELEASE
    • 4.3.26.RELEASE
    • 4.3.27.RELEASE
    • 4.3.28.RELEASE
    • 4.3.29.RELEASE
    • 4.3.30.RELEASE
    • 5.0.0.RELEASE
    • 5.0.1.RELEASE
    • 5.0.2.RELEASE
    • 5.0.3.RELEASE
    • 5.0.4.RELEASE
    • 5.0.5.RELEASE
    • 5.0.6.RELEASE
    • 5.0.7.RELEASE
    • 5.0.8.RELEASE
    • 5.0.9.RELEASE
    • 5.0.10.RELEASE
    • 5.0.11.RELEASE
    • 5.0.12.RELEASE
    • 5.0.13.RELEASE
    • 5.0.14.RELEASE
    • 5.0.15.RELEASE
    • 5.0.16.RELEASE
    • 5.0.17.RELEASE
    • 5.0.18.RELEASE
    • 5.0.19.RELEASE
    • 5.0.20.RELEASE
    • 5.1.0.RELEASE
    • 5.1.1.RELEASE
    • 5.1.2.RELEASE
    • 5.1.3.RELEASE
    • 5.1.4.RELEASE
    • 5.1.5.RELEASE
    • 5.1.6.RELEASE
    • 5.1.7.RELEASE
    • 5.1.8.RELEASE
    • 5.1.9.RELEASE
    • 5.1.10.RELEASE
    • 5.1.11.RELEASE
    • 5.1.12.RELEASE
    • 5.1.13.RELEASE
    • 5.1.14.RELEASE
    • 5.1.15.RELEASE
    • 5.1.16.RELEASE
    • 5.1.17.RELEASE
    • 5.1.18.RELEASE
    • 5.1.19.RELEASE
    • 5.1.20.RELEASE
    • 5.2.0.RELEASE
    • 5.2.1.RELEASE
    • 5.2.2.RELEASE
    • 5.2.3.RELEASE
    • 5.2.4.RELEASE
    • 5.2.5.RELEASE
    • 5.2.6.RELEASE
    • 5.2.7.RELEASE
    • 5.2.8.RELEASE
    • 5.2.9.RELEASE
    • 5.2.10.RELEASE
    • 5.2.11.RELEASE
    • 5.2.12.RELEASE
    • 5.2.13.RELEASE
    • 5.2.14.RELEASE
    • 5.2.15.RELEASE
    • 5.2.16.RELEASE
    • 5.2.17.RELEASE
    • 5.2.18.RELEASE
    • 5.2.19.RELEASE
    • 5.2.20.RELEASE
    • 5.2.21.RELEASE
    • 5.2.22.RELEASE
    • 5.2.23.RELEASE
    • 5.2.24.RELEASE
    • 5.2.25.RELEASE
    • 5.3.0
    • 5.3.1
    • 5.3.2
    • 5.3.3
    • 5.3.4
    • javax.servlet-api:4.x
    • 5.3.5
    • 5.3.6
    • 5.3.7
    • 5.3.8
    • 5.3.9
    • 5.3.10
    • 5.3.11
    • 5.3.12
    • 5.3.13
    • 5.3.14
    • 5.3.15
    • 5.3.16
    • 5.3.17
    • 5.3.18
    • 5.3.19
    • 5.3.20
    • 5.3.21
    • 5.3.22
    • 5.3.23
    • 5.3.24
    • 5.3.25
    • 5.3.26
    • 5.3.27
    • 5.3.28
    • 5.3.29
    • 5.3.30
    • 5.3.31
    • 5.3.32
    • 5.3.33
    • 5.3.34
    • 5.3.35
    • 5.3.36
    • 5.3.37
    • 5.3.38
    • 5.3.39
    • javax.* -> jakarta.*
      jakarta.servlet-api:6.x
      Java 17 min
    • 6.0.0
    • 6.0.1
    • 6.0.2
    • 6.0.3
    • 6.0.4
    • 6.0.5
    • 6.0.6
    • 6.0.7
    • 6.0.8
    • 6.0.9
    • 6.0.10
    • 6.0.11
    • 6.0.12
    • 6.0.13
    • 6.0.14
    • 6.0.15
    • 6.0.16
    • 6.0.17
    • 6.0.18
    • 6.0.19
    • 6.0.20
    • 6.0.21
    • 6.0.22
    • 6.0.23
    • 6.1.0
    • 6.1.1
    • 6.1.2
    • 6.1.3
    • 6.1.4
    • 6.1.5
    • 6.1.6
    • 6.1.7
    • 6.1.8
    • 6.1.9
    • 6.1.10
    • 6.1.11
    • 6.1.12
    • 6.1.13
    • 6.1.14
    • 6.1.15
    • 6.1.16
    • 6.1.17
    • 6.1.18
    • 6.1.19
    • 6.1.20
    • 6.1.21
    • 6.2.0
    • 6.2.1
    • 6.2.2
    • 6.2.3
    • 6.2.4
    • 6.2.5
    • 6.2.6
    • 6.2.7
    • 6.2.8
    • 6.2.9
    • 6.2.10
    • 6.2.11
    • 6.2.12
    • 6.2.13
    • 6.2.14
    • 6.2.15
    • 6.2.16
    • 6.2.17
    • 6.2.18
    • 6.2.19
    • 7.0.0
    • 7.0.1
    • 7.0.2
    • 7.0.3
    • 7.0.4
    • 7.0.5
    • 7.0.6

    Versions in green have been tested.

  • spring-test 7.0.6 (Spring TestContext Framework)
  • jakarta.servlet-api 6.1.0 (Jakarta Servlet API documentation)
  • junit-jupiter-engine 6.0.3 (Module "junit-jupiter-engine" of JUnit)
  • jackson-dataformat-xml 2.21.2 (Data format extension for Jackson to offer alternative support for serializing POJOs as XML and deserializing XML as POJOs)
  • hamcrest 3.0 (Core API and libraries of hamcrest matcher framework)
  • assertj-core 3.26.3 (Rich and fluent assertions for testing in Java)
  • JDK 25
  • Maven 3.9.11

Spring MVC - Convert XML Message to Object using @RequestBody and @ResponseBody Select All Download
  • spring-xml-example
    • src
      • main
        • java
          • com
            • logicbig
              • example
                • UserController.java
        • test
          • java
            • com
              • logicbig
                • example

    See Also

    Join