Yesterday my team had the situation that a deployment failed with a NoSuchMethodError, specifically the method com/google/common/collect/ImmutableList.copyOf could not be found while querying the Confluence REST API.
NoSuchMethodExceptions is thrown when the JVM tries to make a call to a method through Java Reflection API. NoSuchMethodError is thrown when the compiled Java code directly calls the method without using the Reflection API.
Because of its nature the reason for a NoSuchMethodException can be a syntactical issue (e.g.misspelled method name in getDeclaredMethod). If you receive the exception during development, please check the correct spelling of the method name you try to call through reflection.
There are mostly two reasons why this error occurs during runtime:
- The method signature (method name and expected parameters) does exist nowhere in your classpath. There could be an issue in your deployment / packaging phase. For a simple web project which is packaged through Maven this is very unlikely. But if you try to use overlays with classes outside of your POM definition, there could your problem be located.
- The method signature does exist mulitple times in your classpath. It means, you have different versions of the class in your classpath. The classes could have the same method names but can differ in the parameter list.
It highly depends upon on the environment which of the classes in JAR files have precedence. There is no such JVM specification that a classloader has to either fetch JARs in alphabetical or last-touched order or use a first-come/first-serve last-come/first-serve order. For example, JAR files are loaded in Tomcat until <= 7 in alphabetical order. Tomcat 8 let the filesystem make the decision which JAR comes first (Order of loading jar files from lib directory).
To identify the source of the problem, navigate to the main classpath directory of your application (e.g. WEB-INF/lib) and execute
for jar in *.jar; do for class in $(jar -tf $jar | grep $CLAZZ.class | sed 's/.class//g'); do javap -classpath $jar -s $class | grep -A 2 $METHOD && echo $jar.$class; done; done
Replace $CLAZZ with the name of the class and $METHOD with the name of the method. The shell script above searches for every occurence of the method inside any of the JARs and prints out the different signatures.
- If there is no result, you hit the first case: your deployment script did not include the required dependency.
- If there are multiple results from different JAR files, you have to compare the stacktrace of your application logs with the output of the script. Check the dependency hierarchy of your Maven POM and exclude the version not containing the expected method signature.
In our case, I had mistakenly included google-collections-1.0 and guava-11.0.2 in a referenced JAR which both provide ImmutableList. google-collection is the older dependency and does not contain the copyOf method. In the development environment, the (Spring Boot) application has been always executed through the embedded application server. In production, the WAR was deployed inside a Tomcat 8 container. In the end we removed the google-collections from the referenced JAR and the issue has been fixed.
One last word from the Tomcat Bugzilla by Mark Thomas:
Applications that depend on JARs being searched for classes in a particular order are broken and should be fixed.