I need to consume an external REST API that returns not a json (application/json), but an xml (text/xml
to be precise, not application/xml). I'm using the new RestClient
introduced in Spring Boot 3.2.
This is an example of the data that the external api returns:
<Player><NAME>Someone</NAME></Player>
I can't change the response type based on the Accept header, the external api only returns stuff with the text/xml
type. Of course, as it's an external api, I don't have a say in what is the return type.
I created this class:
@Data@AllArgsConstructor@NoArgsConstructor@XmlRootElement(name = "Player")@XmlAccessorType(XmlAccessType.FIELD)public class PlayerModel { @XmlElement(name = "NAME") private String name;}
And I tried this:
RestClient restClient = RestClient.create();PlayerModel result = restClient.get() .uri(url) .retrieve() .body(PlayerModel.class);
But this crashed everything, as everytime I called the external api, it said that it didn't understand how to unmarshall something of type text/xml
.
Then I read from spring docs about REST Clients about HTTP Message Conversion
. There it said that there are many HttpMessageConverter
, and that each one has a default media type that it supports. I guess that whatever RestClient is using by default doesn't support by default the type text/xml
, so I decided to use one of the suggested. I was between MarshallingHttpMessageConverter
and MappingJackson2XmlHttpMessageConverter
.
With MarshallingHttpMessageConverter
I had trouble, as I wanted to use an Unmarshaller made from JAXBContext
(there will be code about this further down), which creates an Unmarshaller from jakarta.xml.bind.Unmarshaller
. However, when creating a MarshallingHttpMessageConverter
, if you try to pass this Unmarshaller to it (it accepts both an Unmarshaller and a Marshaller), it fails because it expects an Unmarshaller from org.springframework.oxm.Unmarshaller
. I don't know about that, but maybe what I want is close to this.
As that didn't quite work, I used MappingJackson2XmlHttpMessageConverter
, like so:
RestClient restClient = RestClient.builder() .messageConverters(converters -> { MappingJackson2XmlHttpMessageConverter converter = new MappingJackson2XmlHttpMessageConverter(); converter.setSupportedMediaTypes(List.of(MediaType.APPLICATION_XML, MediaType.TEXT_XML)); converters.add(converter); }) .build();
Now it doesn't explode, but when checking the variable PlayerModel result
, the name
is null, meaning it didn't catch it (should be Someone
, but is null
).
Lastly I resorted to catching it as a String
, instead of a PlayerModel
, and use a JAXB created Unmarshaller to unmarshall the xml content of the string into a playerModel:
RestClient restClient = RestClient.builder() .messageConverters(converters -> { MappingJackson2XmlHttpMessageConverter converter = new MappingJackson2XmlHttpMessageConverter(); converter.setSupportedMediaTypes(List.of(MediaType.APPLICATION_XML, MediaType.TEXT_XML)); converters.add(converter); }) .build();String result = restClient.get() .uri(url) .retrieve() .body(String.class);PlayerModel playerModel;try { Unmarshaller unmarshaller = JAXBContext.newInstance(PlayerModel.class).createUnmarshaller(); playerModel = (PlayerModel) unmarshaller.unmarshal(new StringReader(result));} catch (JAXBException e) { throw new RuntimeException(e);}return playerModel;
This works, but I want a better way to do it. I know there must be one, but I already used a lot of time for this. Maybe with some Bean configuration, or maybe with another HttpMessageConverter, or another Unmarshaller. The objective is this:
PlayerModel result = restClient.get() .uri(url) .retrieve() .body(PlayerModel.class);
I'd appreciate any help I can get. Thanks :)))