Hi,

This is something that I brought up with Mark Reinhold at Jfokus in
Stockholm the other day. I don't think I was able to formulate my concern
well, and while I've now seen it may be considered a non-requirement in
Jigsaw
<http://openjdk.java.net/projects/jigsaw/goals-reqs/03#multiple-versions>,
I think it's important, so I'm going to see if I can be more coherent in
text.

One of the more common and also hard-to-solve problems that happen when you
have a fairly large, Maven-based ecosystem is that your transitive
dependencies depend on conflicting versions of some jar file. A common
example at Spotify (where we use a lot of Cassandra) is that some
application uses Cassandra version 2.0.5 and a new version of some internal
library, let's call it X. Library X depends on Guava version 18 and uses,
say, com.google.common.base.MoreObjects. Cassandra 2.0.5 depends on Guava
version 15 and uses some method (maybe
com.google.common.io.ByteStreams.newInputSupplier() or something) that has
been removed in Guava 18.

The problem is of course that if Maven decides to include Guava version 18
on the classpath, then you will get an error at runtime due to the method
on ByteStreams having been removed, and if it decides to include version
15, then library X will run into another error because the MoreObjects
class didn't exist before version 18. Another problem is that it's not
obvious to developers how Maven is going to choose which version to include
(I think
http://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html
is
a correct description, but I'm not sure), but that's not really germane to
this discussion.

When this sort of problem happens, it's unpredictable because it depends on
exactly which code paths get hit and it's hard enough to figure out that
people can spend days on it, depending of course on how familiar they are
with this type of thing. Note that since it's dependent on which code gets
executed, it doesn't necessarily start happening immediately when you
include library X, but maybe later on due to some seemingly innocuous code
change that led to execution of a new code path. There are tools like
the enforcer
plugin <http://maven.apache.org/enforcer/maven-enforcer-plugin/> that help
reduce the likelihood of this happening, but I think most Java developers
are unaware of those.

Some variations of the problem:

   1. As above: two incompatible versions are specified; there is no choice
   that will work for all code paths. The developer has to change the
   dependencies somehow.
   2. Library X doesn't depend on MoreObjects or anything else that has
   been added since version 15 of Guava, so there is a best choice (pick
   version 15).
   3. Two Maven artifacts with different group/artifact ID:s define the
   same classes. A real-world example is mockito-all
   
<http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.mockito%22%20AND%20a%3A%22mockito-all%22>,
   which for instance defines Hamcrest classes that would normally be taken
   from hamcrest-core
   
<http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.hamcrest%22%20AND%20a%3A%22hamcrest-core%22>.
   Another variation is when an artifact changes names, such as when
   google-collections became Guava. There are many other examples.

The point I'm trying to make is that I agree with the statement from
JSR-376 that, "Developers have long suffered with the brittle, error-prone
class-path mechanism for configuring program components", but I think that
not addressing the problems above means we will keep suffering. In
practice, the only brittleness problems I have seen people suffering from
in the last 10 years or so are due incorrect dependency resolution leading
to runtime errors. As a developer of developer tools, I really like the
idea of being able to do things like specify which packages I'm exporting
from my modules, but I don't think the people using my modules suffer very
much from me not being able to make the backwards-incompatible changes I
would like in an API that should never have been public. I suffer because
of that, and while that makes me less good at shipping improvements, I
think they lose more from versioning conflicts.

So if nothing else, perhaps the formulation in the Jigsaw goals and
requirements could be changed - the need for a *working* dependency
resolution mechanism is clearly there. Maybe the place where it is best
solved is not in Jigsaw, but it does seem like something that would fit
squarely with the goals of the project and JSR, and it's in fact the only
important classpath-brittleness issue I tend to come across.

Regards,
Petter

Reply via email to