DEV Community

Nilanchal
Nilanchal

Posted on • Originally published at stacktips.com

Working with XML-Based REST API with Spring Boot

The @RestController annotation in Spring boot is designed to automatically serializes Java objects into JSON or XML, based on the content negotiation strategy defined in your controller.

It combines the @Controller and @ResponseBody annotations. It tells Spring that this class is a controller where every method returns a domain object instead of a view.

  • The payload is automatically converted to JSON or XML based on the value defined in the Accept header.
  • Similarly, the response object is automatically converted to JSON or XML-based Content-type header defined in your controller configuration.

Spring uses the Jackson library internally so we don't need to serialise or deserialise to convert Java objects manually.

Watch the full video walkthrough.

For example in the following controller class:

  • It has 3 controller methods; two GET endpoints that return the movie data
  • A POST endpoint that adds a new movie to the MongoDB
  • By default, the controller class will accept the JSON request and produce 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);
    }
}
Enter fullscreen mode Exit fullscreen mode

Let us 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 the Gradle project, add the following dependency to your build.gradle file:

implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml'
Enter fullscreen mode Exit fullscreen mode

For a 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>
Enter fullscreen mode Exit fullscreen mode

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 the produces and consumes property of RequestMapping annotation.

@RestController  
@RequestMapping(value = "/api/1.0/movies",  
        consumes = {MediaType.APPLICATION_XML_VALUE}, # Content-Type
        produces = {MediaType.APPLICATION_XML_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);  
    }  

}
Enter fullscreen mode Exit fullscreen mode

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. We can also do the same to the individual methods by using produces and consumes property on HTTP method mapping annotation.

That is all, now our controller will handle the XML request and produce the 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'
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

Configure Default Content Negotiation

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

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

@Configuration  
public class AppConfig implements WebMvcConfigurer {  

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

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 the response <movies><movie></movie></movies> tag.

  • Create a wrapper class for Movies.
  • The @JacksonXmlRootElement annotation can be used to define the name of the root element used for the root-level object when serialized, which normally uses the name of the type (class).
  • The @JacksonXmlElementWrapper annotation is used to specify XML elements 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;
}
Enter fullscreen mode Exit fullscreen mode

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

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

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>
Enter fullscreen mode Exit fullscreen mode

Top comments (0)