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 ProjectDependencies and Technologies Used: - spring-webmvc 7.0.6 (Spring Web MVC)
Version Compatibility: 3.2.9.RELEASE - 7.0.6 Version compatibilities of spring-webmvc with this example: 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
|