Springboot issues with Content-Type xml & json Request & Response payloads

I am trying to understand how can i fix this particular problem with configuration/dependencies changes.

Java Version: 1.8

Springboot version: 2.7.17

Request & Response Model Schemas are generated from the XSD schema.

I have tried to debug with a small project to debug & looking for some solution

What’s happening:

when I tried XML payloads the Request & Response models were as expected because of @XmlElement Name attributes, however when tried with JSON payloads it respect class fields rather than @XmlElement Name Attribute.

Looking for some solution to fix this particular problem where I will not be allowed to change the definition of XSD, but open to changing the Parsing logic with some kind of configuration change.

Limitations: our XSD files have been maintained for years & POJO classes will be generated using the cxf-codegen-plugin version 3.0.3 and goal wsdl2java . Hence I will not get the green light to change the POJO Schemas. Asked politely to provide a backward-compatible solution.

Here’s the equivalent sample project for easier debugging:

XML payload:

POST http://localhost:9990/api/v1/users/test
Content-Type: application/xml
Accept: application/json

<InputRequest>
    <ACCOUNT_NO>12</ACCOUNT_NO>
    <FLAGS>2</FLAGS>
    <POID>1.0.0</POID>
    <PRODUCTS elem="0">
        <CODE>1400337</CODE>
    </PRODUCTS>
</InputRequest>

JSON payload:

POST http://localhost:9990/api/v1/users/test
Content-Type: application/json
Accept: application/json

{
  "ACCOUNT_NO": "122",
  "FLAGS": 2,
  "POID": "1.0.0",
  "PRODUCTS": {
    "CODE": "1222",
    "ELEM": 0
  }
}

Expected Response:

{
  "ACCOUNT_NO": "122",
  "FLAGS": 2,
  "POID": "1.0.0",
  "PRODUCTS": {
    "CODE": "1222",
    "ELEM": 0
  }
}

Realiity:

{
  "accountno": null,
  "flags": 0,
  "poid": null,
  "products": []
}

POJO (Generated from XSD):

package com.example.demo;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.*;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(
        name = "",
        propOrder = {"accountno", "flags", "poid", "products"}
)
@XmlRootElement(
        name = "InputRequest"
)
public class InputRequest implements Serializable {
    private static final long serialVersionUID = 1L;
    @XmlElement(
            name = "ACCOUNT_NO",
            required = true
    )
    protected String accountno;

    @XmlElement(
            name = "FLAGS"
    )
    protected int flags;
    @XmlElement(
            name = "POID",
            required = true
    )
    protected String poid;
    @XmlElement(
            name = "PRODUCTS",
            required = true
    )
    protected List<PRODUCTS> products;

    public InputRequest() {
    }

    public String getACCOUNTNO() {
        return this.accountno;
    }

    public void setACCOUNTNO(String value) {
        this.accountno = value;
    }


    public int getFLAGS() {
        return this.flags;
    }

    public void setFLAGS(int value) {
        this.flags = value;
    }

    public String getPOID() {
        return this.poid;
    }

    public void setPOID(String value) {
        this.poid = value;
    }

    public List<PRODUCTS> getPRODUCTS() {
        if (this.products == null) {
            this.products = new ArrayList();
        }

        return this.products;
    }

    @XmlAccessorType(XmlAccessType.FIELD)
    @XmlType(
            name = ""
    )
    public static class PRODUCTS extends CODE implements Serializable {
        private static final long serialVersionUID = 1L;
        @XmlAttribute(
                name = "elem"
        )
        protected String elem;

        public PRODUCTS() {
        }

        public String getElem() {
            return this.elem;
        }

        public void setElem(String value) {
            this.elem = value;
        }
    }
    @XmlAccessorType(XmlAccessType.FIELD)
    @XmlType(
            propOrder = {"code"}
    )
     public static class CODE implements Serializable {
        private static final long serialVersionUID = 1L;
        @XmlElement(
                name = "CODE",
                required = true
        )
        protected String code;

        public CODE() {
        }

        public String getCODE() {
            return this.code;
        }

        public void setCODE(String value) {
            this.code = value;
        }
    }

}

Rest Controller:

@RestController
@RequestMapping("/api/v1/users")
public class TestController {

    @ResponseStatus(HttpStatus.CREATED)
    @PostMapping(consumes = {MediaType.APPLICATION_JSON_VALUE,MediaType.APPLICATION_XML_VALUE},value = "/test")
    public InputRequest echoXmlUser(@RequestBody InputRequest payload) throws IOException {
        System.out.println(new ObjectMapper().writeValueAsString(payload));
        return payload;
    }

}

pom.xml (trying with different depdencies)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-parent</artifactId>
       <version>2.7.17</version>
       <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
       <java.version>8</java.version>
    </properties>
    <dependencies>
       <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
       </dependency>

       <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-test</artifactId>
          <scope>test</scope>
       </dependency>
<!--       <dependency>-->
<!--          <groupId>com.fasterxml.jackson.dataformat</groupId>-->
<!--          <artifactId>jackson-dataformat-xml</artifactId>-->
<!--          <version>2.4.0</version>-->
<!--       </dependency>-->
       <dependency>
          <groupId>org.glassfish.jaxb</groupId>
          <artifactId>jaxb-runtime</artifactId>
          <version>2.3.8</version>
       </dependency>
    </dependencies>
    <build>
       <plugins>
          <plugin>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-maven-plugin</artifactId>
             <configuration>
                <image>
                   <builder>paketobuildpacks/builder-jammy-base:latest</builder>
                </image>
             </configuration>
          </plugin>
       </plugins>
    </build>

</project>

I tried to explore few options with jackson-dataformat-xml & jaxb-runtime libraries & trying to adjust configuration to make it work.

I am looking for some options where this POJO remains constant(due to limitations of wider imact by different clients if changed) & also support both XML & JSON payload with the configuration change/depedency change. I also do not want to do much changes inside the controller because i have 50 such different controllers are also needed to get changed if such thing were to be done.

You will need to use the JAX Module to allow Jackson to use JAXB annotations for metadata. With that added as a dependency you need to add an @Bean method to expose the JaxbAnnotationModule so it will be picked up by Spring Boot and added to Jackson.

@Bean
public JaxbAnnotationModule jaxbAnnotationModule() {
  return new JaxbAnnotationModule();
}

The dependency

<dependency>
  <groupId>tools.jackson.module</groupId>
  <artifactId>jackson-module-jaxb-annotations</artifactId>
</dependency>

I suspect that Spring Boot will manage the version of the jackson-module-jaxb-annotations for you (through the included jackson-bom). If not you should be able to use ${jackson.version} as the <version> as that will contain the Jackson version in use for your Spring Boot version.

Leave a Comment