8

I have two spring boot projects - A(Main larger project with APIs) and B(a library which is imported by A as a dependency in pom.xml)

pom.xml of project B:

<?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 http://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.1.6.RELEASE</version>

        <relativePath /> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.b.root</groupId>
    <artifactId>b</artifactId>
    <version>0.0.1</version>
    <packaging>jar</packaging>
    <name>b</name>
    <description>Importable Jar</description>

    <properties>
        <java.version>11</java.version>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <oauth2.version>2.1.1.RELEASE</oauth2.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
                <!-- Removing dependency for the embedded Tomcat -->
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
                <!-- End -->
            </exclusions>
        </dependency>

        <!-- asynchronous loggers -->
        <dependency>
            <groupId>com.lmax</groupId>
            <artifactId>disruptor</artifactId>
            <version>3.4.2</version>
        </dependency>

        <!-- Actuator -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!-- end -->

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.skyscreamer</groupId>
                    <artifactId>jsonassert</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.security.oauth.boot</groupId>
            <artifactId>spring-security-oauth2-autoconfigure</artifactId>
            <version>${oauth2.version}</version>
        </dependency>


        <!--  docusign dependencies -->

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.persistence</groupId>
            <artifactId>javax.persistence-api</artifactId>
            <version>2.2</version>
        </dependency>

        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.5</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.9.6</version>
        </dependency>
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
            <version>2.9.8</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.10</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.1.1</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                        <phase>package</phase>
                        <configuration>
                            <!--to be imported on other projects-->
                            <classifier>b-classifier</classifier>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.5</version>
                <configuration>
                    <skipTests>false</skipTests>
                    <testFailureIgnore>true</testFailureIgnore>
                    <forkMode>once</forkMode>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <forceJavacCompilerUse>true</forceJavacCompilerUse>
                    <source>11</source>
                    <target>11</target>
                </configuration>
            </plugin>
        </plugins>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <filtering>false</filtering>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>false</filtering>
            </resource>
            <resource>
                <directory>src/test/java</directory>
                <filtering>false</filtering>
            </resource>
        </resources>
    </build>

</project>

pom.xml of project A:

<?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>
    <groupId>com.a.root</groupId>
    <artifactId>a</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>a</name>
    <description>Project A</description>

    <properties>
        <java.version>11</java.version>
    </properties>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <dependencies>
        <!-- Springboot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <!-- JUnit -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!-- PostgreSQL -->
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
            <scope>provided</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.5</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.openapitools/jackson-databind-nullable -->
        <dependency>
            <groupId>org.openapitools</groupId>
            <artifactId>jackson-databind-nullable</artifactId>
            <version>0.2.1</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/ch.qos.logback.contrib/logback-json-classic -->
        <dependency>
            <groupId>ch.qos.logback.contrib</groupId>
            <artifactId>logback-json-classic</artifactId>
            <version>0.1.5</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/ch.qos.logback.contrib/logback-jackson -->
        <dependency>
            <groupId>ch.qos.logback.contrib</groupId>
            <artifactId>logback-jackson</artifactId>
            <version>0.1.5</version>
        </dependency>

        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.7</version>
        </dependency>

        <dependency>
            <groupId>org.everit.json</groupId>
            <artifactId>org.everit.json.schema</artifactId>
            <version>1.3.0</version>
        </dependency>

        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
            <version>1.3.1.Final</version>
        </dependency>

        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-processor</artifactId>
            <version>1.3.1.Final</version>
        </dependency>

        <dependency>
            <groupId>com.nimbusds</groupId>
            <artifactId>nimbus-jose-jwt</artifactId>
            <version>9.0.1</version>
        </dependency>

        <dependency>
            <groupId>org.passay</groupId>
            <artifactId>passay</artifactId>
            <version>1.6.0</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- Importing library b -->
        <dependency>
            <groupId>com.b.root</groupId>
            <artifactId>b</artifactId>
            <version>0.0.1</version>
            <classifier>b-classifier</classifier>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

There are APIs in project A that return JSON object. But in integration tests they are returning data in XML format causing them to fail. And this happens only when library B is used in A. If we remove B from A, then all tests pass.

This is the error message:

org.json.JSONException: Unparsable JSON string: <ResponseDTO>.....</ResponseDTO>

My guess is that there is some kind of conflict with dependencies of both projects. But not able to pin point which dependency exactly.

My guess is that as spring uses jackson to convert returning objects into json and there is a dependency in project B, it could have something to do with this library specifically:

    <dependency>
        <groupId>com.fasterxml.jackson.dataformat</groupId>
        <artifactId>jackson-dataformat-xml</artifactId>
        <version>2.9.8</version>
    </dependency>

But I'm not able to understand how would this affect anything. Let me know if some more information is needed.

EDIT 1: Adding code for controller:

TestDTO.java

@Data // lombok annotation
public class TestDTO {

  @JsonProperty(value = "id")
  @JsonSetter(nulls = Nulls.AS_EMPTY)
  private String id;

  @JsonProperty(value = "name")
  @JsonSetter(nulls = Nulls.AS_EMPTY)
  private String name;

}

TestController.java

@RestController
public class TestController {

  @Autowired
  private final TestService testService;

  @GetMapping("/test/:id")
  public TestDTO get(@PathVariable String id) {
    return testService.get(id);
  }

}

**EDIT 2: ** Added request response headers

Request

  HTTP Method = GET
  Request URI = /test/1
   Parameters = {}
      Headers = [Content-Type:"application/json", Content-Length:"616"]
         Body = <no character encoding set>
Session Attrs = {}

Response

       Status = 200
Error message = null
      Headers = [Content-Type:"application/xml;charset=UTF-8"]
 Content type = application/xml;charset=UTF-8
         Body = <TestDTO>...</TestDTO>
rsp
  • 813
  • 2
  • 14
  • 26
  • 3
    What do the controllers look like? Are you using `@RestController` and the `produces` property on the `@(Request|Get|Post)Mapping` annotations? Are you using an `Accept:` header on the requests? – RoToRa Mar 01 '21 at 11:01
  • @RoToRa I have added code in question, it is a very generic controller which we use/see all the time. – rsp Mar 01 '21 at 13:54
  • 2
    What about the `Accept:` header? Have a look at (and post) the request and response headers of an example request. – RoToRa Mar 01 '21 at 14:04
  • 1
    @RoToRa Added request response. No `Accept: ` criteria is defined explicitly. – rsp Mar 01 '21 at 14:21
  • 1
    What happens when you add `produces` to `@GetMapping`? – RoToRa Mar 01 '21 at 14:38
  • 1
    Can't edit actually. I'm debugging an issue. I only have read access to project with APIs (Project A). Where I can do edits is Project B (which is a library used by A). @RoToRa For now, I will remove the `jackson-dataformat-xml` dependency from project B and implement a work around using json instead of xml and see if it works. I'll update if this solution works or not. Thanks for your assistance. – rsp Mar 01 '21 at 14:51
  • What happens when you **use** the `Accept:` header in the request? – RoToRa Mar 01 '21 at 15:37

1 Answers1

13

It might be, because Spring Boot picks up the xml-parsing lib through dependency B.

I would either define the produces attribute in your mappings or configure a WebMvcConfigurer to set json as the default type.

To do so, add a WebMvcConfigurer somewhere. Even in dependency B should be fine, if it is running in the same Spring context. This is for Spring Boot 2.4.2:

@Bean
public WebMvcConfigurer customConfigurer() {
    return new WebMvcConfigurer() {
        @Override
        public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
            configurer.defaultContentType(MediaType.APPLICATION_JSON);
        }
    };
}
Tigerware
  • 3,196
  • 2
  • 23
  • 39
  • 9
    Is there a way to detect how Spring is picking the XML parsing lib instead of json? – rsp Mar 03 '21 at 14:18