1. OVERVIEW

A few days ago I was working on a new Spring Boot web application where some dependencies version and common Maven plugins configuration were inherited from a parent pom. Everything was just fine, APIs would work as intended, Unit and Integration Tests were passing except when generating the Cobertura code coverage report. It didn’t matter how minimalist this Spring Boot app could get, code coverage was just failing attempt after attempt.

Spring Boot, Cobertura's LoggerFactory is not a Logback LoggerContext but Logback is on the classpath error

This posts describes troubleshooting a Spring Boot application and Cobertura dependencies to fix multiple SLF4J bindings - found in the classpath - Failed to load ApplicationContext.

2. SETTING UP CODE COVERAGE VIA COBERTURA

pom.xml:

...
<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    <exclusions>
      <exclusion>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-logging</artifactId>
      </exclusion>
    </exclusions>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
  </dependency>
...
<build>
...
  <plugins>
...
    <!-- Test -->
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-surefire-plugin</artifactId>
    </plugin>
    <plugin>
      <groupId>org.codehaus.mojo</groupId>
      <artifactId>cobertura-maven-plugin</artifactId>
      <version>2.7</version>
    </plugin>
...

This snippet includes spring-boot-starter-log4j2 which transitively brings in slf4j 1.7.x and log4j2 2.7. I have also included maven-surefire-plugin to run tests and cobertura-maven-plugin to report code coverage.

3. THE PROBLEM

presents when generating the cobertura code coverage report:

mvn cobertura:cobertura

A partial output shows:

...
[INFO] >>> cobertura-maven-plugin:2.7:cobertura (default-cli) > [cobertura]test @ demo-springboot-cobertura-issue >>>
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ demo-springboot-cobertura-issue ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
...
[ERROR] SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/Users/ootero/.m2/repository/ch/qos/logback/logback-classic/1.0.13/logback-classic-1.0.13.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/Users/ootero/.m2/repository/org/apache/logging/log4j/log4j-slf4j-impl/2.7/log4j-slf4j-impl-2.7.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [ch.qos.logback.classic.util.ContextSelectorStaticBinder]
...
-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.asimio.cobertura.demo.DemoCoberturaApplicationTests
21:48:37.244 [main] ERROR org.springframework.test.context.TestContextManager - Caught exception while allowing TestExecutionListener [org.springframework.test.context.web.ServletTestExecutionListener@26e356f0] to prepare test instance [com.asimio.cobertura.demo.DemoCoberturaApplicationTests@47d9a273]
java.lang.IllegalStateException: Failed to load ApplicationContext
...
Caused by: java.lang.IllegalArgumentException: LoggerFactory is not a Logback LoggerContext but Logback is on the classpath. Either remove Logback or the competing implementation (class org.apache.logging.slf4j.Log4jLoggerFactory loaded from file:/Users/ootero/.m2/repository/org/apache/logging/log4j/log4j-slf4j-impl/2.7/log4j-slf4j-impl-2.7.jar). If you are using WebLogic you will need to add 'org.slf4j' to prefer-application-packages in WEB-INF/weblogic.xml: org.apache.logging.slf4j.Log4jLoggerFactory
...
Results :

Tests in error:
  DemoCoberturaApplicationTests.contextLoads » IllegalState Failed to load Appli...

Tests run: 1, Failures: 0, Errors: 1, Skipped: 0

[ERROR] There are test failures.

Please refer to /Users/ootero/Projects/bitbucket.org/demo-springboot-cobertura-issue/target/surefire-reports for the individual test results.

As mentioned earlier, even though the application runs as expected, tests passes when run in the IDE and when building the artifact from command line, but running the Maven goal cobertura:cobertura fails because two slf4j bindings are found in the classpath: logback-classic and log4j-slf4j-impl.

4. SOLVING THE ISSUE

log4j-slf4j-impl is transitively included from spring-boot-starter-log4j2 which could be observed when looking at the output of the Maven goal dependency:tree but it doesn’t say much about logback-classic because it’s used by the Cobertura Maven plugin to instrument classes, run the tests and generate the report.

After some troubleshooting, this issue could be fixed by excluding logback-classic from the dependency including it, via Maven’s dependencyManagement section:

pom.xml:

...
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>net.sourceforge.cobertura</groupId>
      <artifactId>cobertura</artifactId>
      <version>2.1.1</version>
      <scope>test</scope>
      <exclusions>
        <exclusion>
          <groupId>ch.qos.logback</groupId>
          <artifactId>logback-classic</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
  </dependencies>
</dependencyManagement>
...

Thanks for reading and as always, feedback is very much appreciated. If you found this post helpful and would like to receive updates when content like this gets published, sign up to the newsletter.

5. SOURCE CODE

Accompanying source code for this blog post can be found at:

6. REFERENCES