I once wrote a static checker for Java. It found all breaking changes in APIs of all my dependencies. Any time functions were removed, parameters were removed, type signatures changed, it would catch them.
It would walk the maven dependency tree to find these, any time if found two versions of a library (say app depends on A and B, A depends on Guava 17, B depends on 18), it would log it as an resolvable issue.
On the project I tested it against, I remember it logging something like 250-1000 issues. I gave up. I'm not even sure where the code is now. Java is supposedly statically checked, but it lacks a real linker, as anyone who has ever seen a run time exception of "XXX is not a function" can tell you.
As far as Maven being "unpredictable", how it would resolve transitive dependency conflicts prior to 2.0 was undefined behaviour, and post 2.0 it is bizarre/lazy at best. It doesn't use the newest version, and doesn't flag major version conflicts. It just topologically sorts the graph and picks the highest dependency. The logic is bizarre. I have no idea if gradle handles it better, but they both face an unsolvable problem.
Shade is a pretty terrible work around. Auto-sandboxing conflicting dependencies in their own class loaders would be the most reasonable solution. As long as return values weren't passed between libraries.
Edit to add:
And this is not a theoretical problem either, as soon as you bring in a big dependency like Spring or Spark you are already 20+ levels deep in the tree, with dozens of these sorts of library conflicts, and hundreds of broken method signatures, just waiting to step on the wrong land-mine. (Not that I bet large ecosystem is any better)