Prevenging SLF4J's "multiple bindings" warning with Gradle

Anyone who has ever tried to configure a reasonably complex Java program will at some point have torn their hair out trying to get rid of warnings such as:

SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/scratch/events-beware/events-beware/build/install/events-beware/lib/logback-classic-1.1.2.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/scratch/events-beware/events-beware/build/install/events-beware/lib/slf4j-log4j12-1.6.1.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/scratch/events-beware/events-beware/build/install/events-beware/lib/slf4j-simple-1.6.1.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]

Usually the unwanted SLF4J bindings are transitive dependencies of your project, so you configure your dependency manager to exclude them. This works for a while, until a new dependency is added to your project, which pulls in yet another unwanted binding...

But what if your build system was able to detect these unwanted extra bindings, and fail the build if they are present? Here's how to do it with Gradle:

$ ./gradlew build
:bundleJars UP-TO-DATE
:compileJava UP-TO-DATE
:compileGroovy UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:jar UP-TO-DATE
:assemble UP-TO-DATE
:checkSlf4j FAILED

FAILURE: Build failed with an exception.

* Where:
Script '/scratch/events-beware/events-beware/slf4j.gradle' line: 14

* What went wrong:
Execution failed for task ':checkSlf4j'.
> Multiple SLF4J bindings found: [logback-classic-1.1.2.jar, slf4j-log4j12-1.6.1.jar, slf4j-simple-1.6.1.jar]

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

BUILD FAILED

Total time: 2.009 secs

To add this to your project, create slf4j.gradle with the following contents:

   1 task checkSlf4j {
   2     description 'Ensure only one SFL4j binding is present in the runtime configuration'
   3 
   4     doLast {
   5         def bindings = []
   6         configurations.runtime.each {
   7             zipTree(it).matching { include 'org/slf4j/impl/StaticLoggerBinder.class' }.each { c ->
   8                 bindings << [it.getName(), c]
   9             }
  10         }
  11         if (bindings.size () > 1) {
  12             throw new GradleException("Multiple SLF4J bindings found: ${bindings*.getAt(0)}")
  13         }
  14     }
  15 }
  16 
  17 check.dependsOn checkSlf4j

And include it in your build.gradle by adding apply from: 'slf4j.gradle'.

robots.org.uk: GradleCheckSlf4j (last edited 2014-08-15 23:13:42 by sam)

© Sam Morris <sam@robots.org.uk>.
Content may be distributed and modified providing this notice is preserved.