Search code examples
spring-bootxml-parsingunmarshallingspring-webclient

UnmarshalException when trying to call API with XML Response from Spring Boot using WebClient


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>

enter image description here

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

enter image description here

I have spent a couple of hours trying to figure whats going wrong here. Any help will be appreciated


Solution

  • 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;