Dmitry
Dmitry

Reputation: 55

Spring Boot REST endpoint ignores Content-Type

I have a problem with creating a simple REST controller with application/xml type. I'm using Kotlin and registered Jackson converter by providing dependency in Gradle KTS

"implementation"(group = "com.fasterxml.jackson.dataformat", name = "jackson-dataformat-xml", version = jacksonXmlVersion)

Controller uses Swagger and looks like this:

@ApiOperation(
    value = "Epcis capture point",
    response = EPCISDocResponse::class,
    httpMethod = "POST"
)
@PostMapping(
    "/capture",
    consumes = [MediaType.APPLICATION_XML_VALUE],
    produces = [MediaType.APPLICATION_XML_VALUE]
)
fun capture(@RequestBody request: EPCISDocumentType): ResponseEntity<EPCISDocResponse> {
    doSomething()
}

My model class is generated by XJC and looks like this:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "EPCISDocumentType", propOrder = {
    "epcisHeader",
    "epcisBody",
    "extension",
    "any"
})
@XmlRootElement(name = "EPCISDocument", namespace = "urn:epcglobal:epcis:xsd:1")
public class EPCISDocumentType
    extends Document
{

The problem is that Spring Boot fails to find the corresponding converter for application/xml and tries to parse it as JSON, giving me the next error:

.w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize instance of java.util.ArrayList<java.lang.Object> out of VALUE_STRING token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of java.util.ArrayList<java.lang.Object> out of VALUE_STRING token at [Source: (PushbackInputStream); line: 4, column: 2] (through reference chain: epcis.capture.EPCISDocumentType["any"])]

I have a Jaxb2 marshaller defined for SOAPTemplate, that can potentially interfere:

@Bean
fun marshaller(): Jaxb2Marshaller? {
    val marshaller = Jaxb2Marshaller()
    marshaller.setClassesToBeBound(
        epcis.capture.EPCISDocumentType::class.java,
        CaptureEPCISDocResponse::class.java,
        LeanEPCISDocResponse::class.java
    )
    return marshaller
}

Any help is much appreciated, it's the first time I see this issue with Jackson mapping to content-type.

List of converters registered in application context:

org.springframework.http.converter.StringHttpMessageConverter@531bfe4b
org.springframework.http.converter.ResourceHttpMessageConverter@2c8f7d6a
org.springframework.http.converter.ResourceRegionHttpMessageConverter@77331437
org.springframework.http.converter.xml.SourceHttpMessageConverter@98a0842
org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter@56f569e
org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter@525c0f74
org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@3da12ba4

UPDATE

I was able to fix this by removing MappingJackson2XmlHttpMessageConverter, because my models contain XmlRootElement and should be marshalled by Jaxb2RootElementHttpMessageConverter, but now the problem is much weirder. Now, instead of marshalling it into my object, Jaxb2Root converter provides my controller with JAXBElement:

Method [public org.springframework.http.ResponseEntity<com.movilizer.lean.model.epcis.LeanEPCISDocResponse> com.movilizer.lean.leanepcis.controller.EpcisController.capture(epcis.capture.EPCISDocumentType)] with argument values:
 [0] [type=javax.xml.bind.JAXBElement] [value=javax.xml.bind.JAXBElement@76e6ecc4] ] with root cause

From my findings, it looks like specific sun jaxb-impl is responsible for this issue, so the parser provides JAXBElement instead of the payload itself. Any help?

Upvotes: 2

Views: 339

Answers (1)

Dmitry
Dmitry

Reputation: 55

I gave up on the idea of making Jaxb2RootElementHttpMessageConverter work well, so I simply changed my rest controller to accept String value:

private val unmarshaller = javax.xml.bind.JAXBContext.newInstance(EPCISDocumentType::class.java).createUnmarshaller()

@PostMapping(
    "/capture",
    consumes = [MediaType.APPLICATION_XML_VALUE],
    produces = [MediaType.APPLICATION_XML_VALUE]
)
fun capture(@RequestBody payload: String): ResponseEntity<String> {
    val request = (unmarshaller.unmarshal(StringReader(payload)) as JAXBElement<EPCISDocumentType>).value
}

Sadly I was not interested in debugging tools that should work out of the box, but do not. I'd like to have a "valid" solution without implementing custom converters but looks like there's something wrong with JAXB-specific implementations and Spring integration.

Upvotes: 0

Related Questions