19

This is a question sounds like bunch of similar questions on SE sites, so I should be quite verbose to make my question clear. So, here is project's minimal pom.xml:

<dependencies>
     <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.0.6</version>
    </dependency>

   <dependency>
        <groupId>org.codehaus.gmaven.runtime</groupId>
        <artifactId>gmaven-runtime-1.7</artifactId>
        <version>1.3</version>
   </dependency>

</dependencies>
<build>
    <plugins>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>exec-maven-plugin</artifactId>
            <version>1.2.1</version>
            <configuration>
                <mainClass>org.shabunc.App</mainClass>
            </configuration>
        </plugin>
    </plugins>
</build>

Here is the dependency tree produced by maven.

mvn dependency:tree -Dverbose -Dincludes=org.slf4j:

[INFO] [dependency:tree {execution: default-cli}]
[INFO] org.shabunc:logdebug:jar:1.0-SNAPSHOT
[INFO] \- ch.qos.logback:logback-classic:jar:1.0.6:compile
[INFO]    \- org.slf4j:slf4j-api:jar:1.6.5:compile

Now, let's remove exclusion and check dependencies again. We'll get:

 [INFO] org.shabunc:logdebug:jar:1.0-SNAPSHOT
[INFO] +- ch.qos.logback:logback-classic:jar:1.0.6:compile
[INFO] |  \- org.slf4j:slf4j-api:jar:1.6.5:compile
[INFO] \- org.codehaus.gmaven.runtime:gmaven-runtime-1.7:jar:1.3:compile
[INFO]    +- (org.slf4j:slf4j-api:jar:1.5.10:compile - omitted for conflict with 1.6.5)
[INFO]    +- org.codehaus.gmaven.feature:gmaven-feature-support:jar:1.3:compile
[INFO]    |  \- (org.slf4j:slf4j-api:jar:1.5.10:compile - omitted for conflict with 1.6.5)
[INFO]    \- org.codehaus.gmaven.runtime:gmaven-runtime-support:jar:1.3:compile
[INFO]       +- (org.slf4j:slf4j-api:jar:1.5.10:compile - omitted for conflict with 1.6.5)
[INFO]       \- org.sonatype.gshell:gshell-io:jar:2.0:compile
[INFO]          \- org.sonatype.gossip:gossip:jar:1.0:compile
[INFO]             \- (org.slf4j:slf4j-api:jar:1.5.8:compile - omitted for conflict with 1.6.5)

So, as we can see, everything works as expected, and conflicting dependency is actually get excluded. But the thing is that even with dependency excluded I still get following message while compiling and calling mvn exec:java:

SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/home/shabunc/.m2/repository/ch/qos/logback/logback-classic/1.0.6/logback-classic-1.0.6.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/home/shabunc/.m2/repository/org/sonatype/gossip/gossip/1.0/gossip-1.0.jar!/org/slf4j/impl/StaticLoggerBinder.class]

The question is: Why I still see this warning and what exactly should be done to make only one version of slf4j reachable during execution?

Community
  • 1
  • 1
shabunc
  • 23,119
  • 19
  • 77
  • 102
  • 2
    No offense intended but declaring a dependency on slf4j-api and then excluding slf4j-api is wrong. The following statement is also incorrect. "let us make sure that slf4j-api is indeed loaded only once, just as we expected when we've added exclusions/exclusion part to gmaven-runtime dependency." When dependencioes declare different versions of an artifact, e.g. slf4j-api, Maven will not place slf4j-api multiple times on the class path. Even if Maven did (which it doesn't), slf4j-api would not be loaded multiple times. Please edit your question as not to mislead future readers. – Ceki Aug 03 '12 at 06:46
  • 1
    Ceki, it will be OK even if it will be offense that will teach me something ))) The fact is slf4j-api is loaded multiple time thus creating conflicts when I'm trying to deploy as war to tomcat. One slf4j is in logblack and one in the dependency of the dependency of gmaven-runtime. If I am missing something, it will be very kind of you if you'll make it clearer, since all this dependency resolving is not the thing I am an expert in. – shabunc Aug 03 '12 at 08:10
  • 1
    If you are using Maven there is no risk that different versions of slf4j-api being present on your class path. However, it is common for different slf4j *bindings*, e.g. slf4j-jdj14.jar, slf4j-log4j.jar or logback-classic.jar to be present simultaneously on the class path. In your case, you have logback-classic-1.0.6.jar gossip-1.0.jar present. Tom Anderson has already provided a good answer for excluding 'gossip'. – Ceki Aug 03 '12 at 08:20
  • 1
    Sorry, may be I just don't get you right, but it looks like that this is exactly how you claim it can not be: there are different SLF4J bindings - SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation. SLF4J: slf4j-api 1.6.x (or later) is incompatible with this binding. SLF4J: Your binding is version 1.5.5 or earlier. SLF4J: Upgrade your binding to version 1.6.x. or 2.0.x – shabunc Aug 03 '12 at 10:47
  • What is possible and common: slf4j-api and its binding having different versions. Example: slf4j-api-1.5.jar and slf4j-simple-1.6.jar being present on the class path. What is uncommon and automatically prevented by Maven: two different versions of slf4j-api present on the classpath. Even if two slf4j-api.jar files with different versions are present, only one will be loaded by the JVM. – Ceki Aug 03 '12 at 11:02
  • there are exactly two slf4j-api.jar loaded, and I've finally got your point, sure only one will be loaded by JVM. It just that in my case it tries to load the wrong one) @Ceki, what kind of edit will be appropriate - "let us make sure that Class path does not contains multiple SLF4J bindings" - is this a valid statement? – shabunc Aug 03 '12 at 11:10
  • And anyway, @Ceki, thank a lot both for logback and SLF4J projects, they indeed deliver a major improvement to a coder's user experience. – shabunc Aug 03 '12 at 11:12
  • 3
    Have you noticed that the output produced by "mvn dependency:tree" contains lines like "org.slf4j:slf4j-api:jar:1.5.10:compile - omitted for conflict with 1.6.5" ? When Maven detects that conflicting (read distinct) versions of an artifact are pulled-in it will retain the version declared nearest to the current project's pom. The other declaration for the artifact with other versions are simply ignored (=omitted). Thus, instead of excluding slf4j-api in "gmaven-runtime" (which is really awful) you should explicitly declare a dependency on slf4j-api in your project's pom. – Ceki Aug 03 '12 at 11:49
  • I edited the question removing misleading clutter but retaining the gist of the question – Ceki Aug 03 '12 at 11:53

3 Answers3

23

Your problem isn't getting two copies of the SLF4J API, it's getting two different SLF4J implementations. You need to exclude Gossip, not the API. That means something like:

<dependency>
    <groupId>org.codehaus.gmaven.runtime</groupId>
    <artifactId>gmaven-runtime-1.7</artifactId>
    <version>1.3</version>
    <exclusions>
      <exclusion>
        <groupId>org.sonatype.gossip</groupId>
        <artifactId>gossip</artifactId>
      </exclusion>
    </exclusions>
</dependency>

The Gossip dependency is declared by gshell-io; hopefully, it doesn't actually need Gossip, it just needs an SLF4J SLF4J, which you are supplying in the shape of Logback.

Tom Anderson
  • 46,189
  • 17
  • 92
  • 133
  • so, you mean I can exclude gossip dependency for free, without any dramatic consequences? ))) Well, and if there are even more dependencies which, in turn, depend on slf4j, it will quickly become, well, a nightmare ((( – shabunc Aug 01 '12 at 12:37
  • 2
    The only way to make SLF4J happy is to either remove your dependency on Logback, or exclude gshell-io's dependency on Gossip. However, i can't promise that this won't have dramatic consequences. I would hope it doesn't - it would be very poor for a library to have a direct dependency on a logging implementation. – Tom Anderson Aug 01 '12 at 12:39
  • The issue is because of 2 different providers... gossip jar has a slf4j implementation within it.. so you either should remove logback or gossip from your dependency tree.. – Byter Aug 01 '12 at 12:41
  • 1
    The way you can check if gshell-io has a real Gossip dependency is to download the jar (or the source), and search for references to types from Gossip. If there are none, you are safe. If there are some, you might still be safe it the code was carefully written. – Tom Anderson Aug 01 '12 at 12:41
  • 2
    However, i have just had a look, and it seems that it was not carefully written. There are direct dependencies - see [Closer.log](http://grepcode.com/file/repo1.maven.org/maven2/org.sonatype.gshell/gshell-io/2.0/org/sonatype/gshell/io/Closer.java#36), [StreamJack.log](http://grepcode.com/file/repo1.maven.org/maven2/org.sonatype.gshell/gshell-io/2.0/org/sonatype/gshell/io/StreamJack.java#40) and [Flusher.log](http://grepcode.com/file/repo1.maven.org/maven2/org.sonatype.gshell/gshell-io/2.0/org/sonatype/gshell/io/Flusher.java#36). This is really bad. Whoever wrote that code should go to jail. – Tom Anderson Aug 01 '12 at 12:44
  • @TomAnderson, I guess this is the answer, thank you very much. Yet I have one last question. You've said that "it would be very poor for a library to have direct dependency on a logging". Does this mean that it's almost always to wrap logging dependency, thus creating transitive dependency? – shabunc Aug 01 '12 at 12:46
  • 2
    @shabunc: What i mean is that the code can have a direct dependency on a logging API (in this case, SLF4J), but should not depend on a specific implementation. That way, it's up to the user of the code (in this case, you) to choose the right implementation. It's even okay for a library to specify a runtime dependency on a particular implementation, to supply a default, but it should tolerate that dependency being excluded and replaced. It's like when you use JDBC - you depend on the JDBC API, but not on classes from a particular driver. – Tom Anderson Aug 01 '12 at 12:52
  • @TomAnderson, thanks again. It's a pity I can not upvote twice :) – shabunc Aug 01 '12 at 12:53
  • 1
    PS to Jason Dillon in case he ever reads this comment thread: i don't *actually* think you should go to jail. That's just a figure of speech hyperbolically expressing the opinion that improvements could be made to the code in question. I think community service or a fine would be sufficient. – Tom Anderson Aug 01 '12 at 13:08
4

All you need to do is add something like this

compile "org.sonatype.gossip:gossip:1.0" {
    exclude module:'slf4j-jcl'
    exclude module:'slf4j-log4j12'
}
Tom Anderson
  • 46,189
  • 17
  • 92
  • 133
Byter
  • 1,132
  • 1
  • 7
  • 12
  • 1
    The issue is caused due to transitive dependency of gossip jar used. The above code will exclude the dependency of sl4j implementation from gossip. – Byter Aug 01 '12 at 12:23
  • 3
    "Something like this" here means "the Maven equivalent of this Gradle code"! – Tom Anderson Aug 01 '12 at 12:27
  • 1
    @TomAnderson ya...sorry i didnt mention that :) – Byter Aug 01 '12 at 12:29
  • 2
    @Byter can you, please, provide a maven equivalent, since I still don't get what do you exactly mean. If you mean I should add this exclusions as well, this won't work - as I've already tell in the question, dependency:tree already does not consider gossip/slf4j dependency to be a part of the project. – shabunc Aug 01 '12 at 12:33
2

I guess you need to play with scope of dependencies, see: http://www.mojohaus.org/exec-maven-plugin/java-mojo.html

classpathScope - defines the scope of the classpath passed to the plugin. Set to compile,test,runtime or system depending on your needs.

kenorb
  • 155,785
  • 88
  • 678
  • 743
Andrey Borisov
  • 3,160
  • 18
  • 18
  • 1
    can you, please, be more specific, I still don't get how setting classpathScope will help resolve this very problem. – shabunc Aug 01 '12 at 12:17