javamongodbspring-bootdependenciesnosuchmethoderror

Java NoSuchMethodError - Imported Third Party Library Call Wrong Package (There Are Packagaes With Same Name)


I failed to run my Java app due to NoSuchMethodError exception. Exception is coming from a third party library initializing an object which has dependency to a certain class name.

This certain class name is actually available from two different transitive packages from two different libraries.

Let say there is a dependency mapping like this:

Instead of using its own dependency mapping, somehow library A uses the dependency of class B on second point.

This is the full error logs.

Description:

An attempt was made to call a method that does not exist. The attempt was made from the following location:

    com.mongodb.MongoClientOptions.<init>(MongoClientOptions.java:157)

The following method did not exist:

    'com.mongodb.connection.ConnectionPoolSettings$Builder com.mongodb.connection.ConnectionPoolSettings$Builder.maxWaitQueueSize(int)'

The calling method's class, com.mongodb.MongoClientOptions, was loaded from the following location:

    jar:file:/Users/rian.krishandi/.gradle/caches/modules-2/files-2.1/org.mongodb/mongo-java-driver/3.12.5/7299fe510c0c7d9b228297ddc41a359909899388/mongo-java-driver-3.12.5.jar!/com/mongodb/MongoClientOptions.class

The called method's class, com.mongodb.connection.ConnectionPoolSettings$Builder, is available from the following locations:

    jar:file:/Users/rian.krishandi/.gradle/caches/modules-2/files-2.1/org.mongodb/mongodb-driver-core/4.4.2/331b96942f7fac72d4334b38176890c4b80cb7dc/mongodb-driver-core-4.4.2.jar!/com/mongodb/connection/ConnectionPoolSettings$Builder.class
    jar:file:/Users/rian.krishandi/.gradle/caches/modules-2/files-2.1/org.mongodb/mongo-java-driver/3.12.5/7299fe510c0c7d9b228297ddc41a359909899388/mongo-java-driver-3.12.5.jar!/com/mongodb/connection/ConnectionPoolSettings$Builder.class

The called method's class hierarchy was loaded from the following locations:

    com.mongodb.connection.ConnectionPoolSettings.Builder: file:/Users/rian.krishandi/.gradle/caches/modules-2/files-2.1/org.mongodb/mongodb-driver-core/4.4.2/331b96942f7fac72d4334b38176890c4b80cb7dc/mongodb-driver-core-4.4.2.jar


Action:

Correct the classpath of your application so that it contains compatible versions of the classes com.mongodb.MongoClientOptions and com.mongodb.connection.ConnectionPoolSettings$Builder

Anyone knows how to fix this issue?

Libraries I used:

I expect the app is running fine without exception.


Solution

  • At runtime, ordinarily, java just tosses it all on a big pile, hence, if you depend (transitively or not) on library 'SomeLib' version X, and you also depend on 'SomeLib' version Y, where X and Y aren't compatible, then it's just not going to work and you need to put in a lot of effort to fix it.

    Option A: Unify

    Update some of your dependencies (possibly using 'overrides' if the reason you depend on SomeLib@X is because it's a 3-ways-removed transitive (you want to use LibA@most recent version, which itself needs LibB@v20, which needs LibC@v5, and that in turn requires SomeLib@X, which is the problem. it is possible/likely that LibA's dep on LibB@v20 could be upgraded to use LibB@v25 instead (that this doesn't break stuff), and that needs LibC@8, and that depends on SomeLib@Y, thus getting rid of your SomeLib@X problem.

    There are various dependency chain checkers; they make nice graphs showing how your LibA ends up needing SomeLib@X via LibB and LibC. You can use these to figure out which deps you want to try newer versions of, or alternatively if they are already at newest, which of their deps you can force upwards.

    If this is not possible, the more drastic solution is to downgrade whatever dependency(ies) require SomeFoo@Y.

    Option B: Per-dep-shading

    If LibA is causing a problem (directly or transitively pulling SomeFoo@X), as long as its API does not communicate in terms of SomeFoo (none of its parameters and none of its return types amongst methods you actually invoke), you can use a shading tool to rewrite com.someFoo.SomeClass into shade.lib.libA.com.someFoo.SomeClass in every constant pool of LibA and its chain of dependencies, and at the same time rename the class itself. Customarily all of libA's deps are not just modified like that but then also packed up into one much larger jar file that now appears to have no further dependencies.

    Folks usually do this to their entire project to end up with a single jar, but you can do that to a single dependency as well, and now a JVM can load conflicting versions, because one of them is no longer called com.someFoo.SomeClass.

    Option C: ClassLoaders

    JVMs can load 2 classes with the same name - by using class loaders. OSGi is one such framework, that uses classloaders to modularize a project such that individual parts can't see each other at all, and can therefore both load a library without that being a 'global choice'. JVM Runtimes don't do this by default and there is no way to make them do this with a setting - you do it with code, writing a ClassLoader (public class MyLoader extends ClassLoader) and carefully managing the fact that any types involved in chat between libraries (such as java.lang.String) isn't loaded separately - that has to be loaded 'globally' (by the parent loader - classloaders have a hierarchy). This is rather tricky, so I suggest some framework or other.

    One advantage of classloaders is that you can make a new one and thus load the same class again - many web frameworks therefore have this setup, because it means you can just 'reload' a web page without shutting down the JVM and still see the effects of that code update you just made: It's convenient for debugging if you aren't using a hot-code-replace debugger like eclipse has. There are also more generalized 'app frameworks' that are designed to let you run lots of simple applications on a single JVM.

    That's another source of ready-made classloader-based runtime-modularization systems.

    But note that especially those web framework getups aren't necessarily designed to enable this, they just do it 'by accident'. And the current trend is to microservice everything.

    Option D: Microservices

    Let your libraries chat to each other over HTTPS and run a ton of little JVMs on multiple machines in an IAAS fleet setup. This is incredibly inefficient (JVMs have overhead; not to mention virtual machines. Running multiple JVMs on a single physical box is inefficient) and is mostly a silly application of Conway's Law - a solution to a development management problem. But then I might just be old man yelling at cloud (literally) on that one.

    Still, it of course solves the problem too. Each microservice is in its own JVM and thus no library conflicts to trip you up.