StackTips

Handling XML Request and Response in Spring Boot REST

Feb 14, 2024 Spring Boot 3.05K 

Spring boot @RestController is designed for automatic serialization and deserialization of Java objects to and from JSON. It uses Jackson library internally and we don't need to manually do anything for convert java objects to JSON while sending response back to to users.

For example in the following controller class;

  • It has get 3 controller methods; two GET endpoints that returns the movies data
  • A POST endpoint that ads a new movie into the database
  • By default, the controller class will accept the JSON request and produces the JSON response.
@RestController
@RequestMapping(value = "/api/1.0/movies")
public class MoviesController {

    private final MovieService movieService;

    public MoviesController(MovieService movieService) {
        this.movieService = movieService;
    }

    @GetMapping
    public ResponseEntity<List<Movie>> getMovies() {
        return ResponseEntity.ok(movieService.getMovies());
    }

    @PostMapping
    public Movie createMovie(@RequestBody MovieDto movieDto) {
        return movieService.createMovie(movieDto);
    }

    @GetMapping(path = "/{movieId}")
    public Movie getMovie(@PathVariable String movieId) {
        return movieService.getMovie(movieId);
    }
}

Let us now change the MovieController to accept XML content type in the request body and produce the XML response.

Jackson XML Dependency

First, we need to add Jackson XML dependency for reading and writing XML data.

For Gradle project, add the following dependency to your build.gradle file:

implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml'

For maven based project, you can add the following to your pom.xml file.

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

And we need to annotate our controller mapping to match the application/xml media type. This is done using the Content-Type and Accept media type to our controller mapping.

This can be done by defining the appropriate MediaType using produces and consumes property of RequestMapping annotation.

@RestController  
@RequestMapping(value = "/api/1.0/movies",  
        consumes = {MediaType.APPLICATION_XML_VALUE},  # Content-Type
        produces = {MediaType.APPLICATION_JSON_VALUE}  # Accept
)  
public class MoviesController {  

    private final MovieService movieService;  

    public MoviesController(MovieService movieService) {  
        this.movieService = movieService;  
    }  

    @GetMapping
    public ResponseEntity<List<Movie>> getMovies() {  
        return ResponseEntity.ok(movieService.getMovies());  
    }  

    @PostMapping
    public Movie createMovie(@RequestBody MovieDto movieDto) {  
        return movieService.createMovie(movieDto);  
    }  

    @GetMapping(path = "/{movieId}")  
    public Movie getMovie(@PathVariable String movieId) {  
        return movieService.getMovie(movieId);  
    }  

}

In the above code snippet, we have set the MediaType configuration to the controller level, which means all controller methods will now consume and produce XML output.

But, we can also do the same to individual @GetMapping, @PostMapping methods.

That is all, now our controller will handle the XML request and produce application/xml media type. Let us test our /movies endpoint

curl --location 'http://localhost:8080/api/1.0/movies' \
    --header 'Content-type: application/xml' \
    --header 'Accept: application/xml'

Now it will produce XML output

<List>
    <item>
        <id>65c4092af4ba290f3c55cd06</id>
        <title>Iron Man &amp; Captain America: Heroes United</title>
        <headline>Iron Man (Adrian Pasdar) and Captain America (Roger Craig Smith) must prevent Red Skull (Liam O'Brien) and Taskmaster (Clancy Brown) from destroying the world.</headline>
        <language>EN</language>
        <region>USA</region>
        <actors>
            <actors>David Kaye</actors>
            <actors>Ian McKellen</actors>
            <actors>Adrian Pasdar</actors>
        </actors>
        <genres>
            <genres>Action</genres>
            <genres>Adventure</genres>
            <genres>Sci-fi</genres>
        </genres>
    </item>
    <item>
        <id>65c4092af4ba290f3c55cd07</id>
        <title>Iron Man &amp; Captain America: Heroes United</title>
        <headline>Iron Man (Adrian Pasdar) and Captain America (Roger Craig Smith) must prevent Red Skull (Liam O'Brien) and Taskmaster (Clancy Brown) from destroying the world.</headline>
        <language>EN</language>
        <region>USA</region>
        <actors>
            <actors>David Kaye</actors>
            <actors>Ian McKellen</actors>
            <actors>Adrian Pasdar</actors>
        </actors>
        <genres>
            <genres>Action</genres>
            <genres>Adventure</genres>
            <genres>Sci-fi</genres>
        </genres>
    </item>
</List>

Configure Default Content Negotiation

The above method works fine but the configuration is now at controller level. The default media type is still remains JSON for all other controllers.

We can override this by setting the default content negotiation for all controllers thought the project by implementing WebMvcConfigurer configuration.

@Configuration  
public class AppConfig implements WebMvcConfigurer {  

    @Override  
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) { 
        configurer.defaultContentType(MediaType.APPLICATION_XML);  
    }  
}

Wrapping XML Response

Notice the above XML response, the result is wrapped inside <List><item><item></List> tag. This is not very pretty.

We can make the following changes to wrap response <movies><movie></movie></movies> tag.

  • Create a wrapper class for Movies.
  • The @JacksonXmlRootElement annotation can be used to define name of root element used for the root-level object when serialized, which normally uses name of the type (class).
  • The @JacksonXmlElementWrapper annotation is used to specifying XML element to use for wrapping List and Map properties.
  • From the controller, instead of returning ResponseEntity<List<Movie>> we will return the ResponseEntity<Movies>> type.
@Getter
@Setter
@RequiredArgsConstructor
@JacksonXmlRootElement(localName = "movies")
public class Movies {

    @JacksonXmlProperty(localName = "movie")
    @JacksonXmlElementWrapper(useWrapping = false)
    private final List<Movie> moviesList;

}

@Getter
@Setter
@JacksonXmlRootElement(localName = "Movie")
public class Movie {
    private String id;
    private String title;
    private String headline;
    private String language;
    private String region;
    private List<String> actors;
    private List<String> genres;
}

And, update the controller to return ResponseEntity<Movies>> type.

@GetMapping  
public ResponseEntity<Movies> getMovies() {  
    Movies movies = new Movies(movieService.getMovies());  
    return ResponseEntity.ok(movies);  
}

Now this will produce,

<movies>
    <movie>
        <id>65c4092af4ba290f3c55cd06</id>
        <title>Iron Man &amp; Captain America: Heroes United</title>
        <headline>Iron Man (Adrian Pasdar) and Captain America (Roger Craig Smith) must prevent Red Skull (Liam O'Brien) and Taskmaster (Clancy Brown) from destroying the world.</headline>
        <language>EN</language>
        <region>USA</region>
        <actors>
            <actors>David Kaye</actors>
            <actors>Ian McKellen</actors>
            <actors>Adrian Pasdar</actors>
        </actors>
        <genres>
            <genres>Action</genres>
            <genres>Adventure</genres>
            <genres>Sci-fi</genres>
        </genres>
    </movie>
    <movie>
        <id>65c4092af4ba290f3c55cd07</id>
        <title>Iron Man &amp; Captain America: Heroes United</title>
        <headline>Iron Man (Adrian Pasdar) and Captain America (Roger Craig Smith) must prevent Red Skull (Liam O'Brien) and Taskmaster (Clancy Brown) from destroying the world.</headline>
        <language>EN</language>
        <region>USA</region>
        <actors>
            <actors>David Kaye</actors>
            <actors>Ian McKellen</actors>
            <actors>Adrian Pasdar</actors>
        </actors>
        <genres>
            <genres>Action</genres>
            <genres>Adventure</genres>
            <genres>Sci-fi</genres>
        </genres>
    </movie>
</movies>
nilan avtar

Nilanchala

I'm a blogger, educator and a full stack developer. Mainly focused on Java, Spring and Micro-service architecture. I love to learn, code, make and break things.