I am trying to call an API that returns an XML Response using WebClient in a Spring Boot application. I am sure I am getting a 200 for the API call but the code is unable to parse the response into a Java POJO (Definitions). I get the error as below
threw exception [Request processing failed: org.springframework.core.codec.DecodingException: Could not unmarshal XML to class com.concur.models.Definitions] with root cause
jakarta.xml.bind.UnmarshalException: unexpected element (uri:"http://www.concursolutions.com/api/expense/extract/2010/02", local:"definitions"). Expected elements are <{}definition>,<{}definitions>
Below are my code snippets
API invocation using WebClient
String definitionURL = "/api/expense/extract/v1.0";
Definitions definitions = webClient
.get()
.uri(definitionURL)
.accept(MediaType.APPLICATION_XML)
.headers(httpHeaders -> httpHeaders.setBearerAuth("<<AUTH>>"))
.retrieve()
.bodyToMono(Definitions.class)
.block();
log.info("Response is {}", definitions);
WebClient Bean configuration
@Bean
public WebClient webClient() {
return WebClient.builder()
.exchangeStrategies(
ExchangeStrategies.builder()
.codecs(clientCodecConfigurer -> clientCodecConfigurer.defaultCodecs().jaxb2Decoder(new Jaxb2XmlDecoder()))
.build())
.baseUrl(concurApiURL)
.build();
}
My Response Models (Definitions.java)
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlRootElement;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@XmlRootElement(name = "definitions")
@XmlAccessorType(XmlAccessType.FIELD)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Definitions {
public List<Definition> definition;
public String xmlns;
public String i;
}
Definition.java
import jakarta.xml.bind.annotation.XmlRootElement;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@XmlRootElement(name = "definition")
public class Definition {
private String id;
private String name;
private String joblink;
}
And lastly the Response XML from the API
<definitions xmlns="http://www.concursolutions.com/api/expense/extract/2010/02" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<definition>
<id>https://us2.concursolutions.com/api/expense/extract/v1.0/gWur87rvH6sJ7PAGiQwjZXFeYCXObXKvVow</id>
<name>ADV AP/GL Extract V.2.4.3</name>
<job-link>https://us2.concursolutions.com/api/expense/extract/v1.0/gWur87rvH6sJ7PAGiQwjZXFeYCXObXKvVow/job</job-link>
</definition>
</definitions>
Also when I remove the line
@XmlAccessorType(XmlAccessType.FIELD)
from my Definitions Model, I get a different error as below
org.glassfish.jaxb.runtime.v2.runtime.IllegalAnnotationsException: 3 counts of IllegalAnnotationExceptions
I have spent a couple of hours trying to figure whats going wrong here. Any help will be appreciated
I was able to get the above working by adding the above working by adding a package-info.java with the namespace definition using the @XmlSchema annotation in my java package folder.
Something like below
@XmlSchema(
namespace = "http://www.concursolutions.com/api/expense/extract/2010/02",
elementFormDefault = XmlNsForm.QUALIFIED
)
package com.concur.models.xml;