Search code examples
javaclasspathguavanosuchmethoderror

Method that exists...does not?


Code (which compiles):

    for (Method m : ImmutableList.class.getMethods()) {
        System.out.println(m);
    }

    ImmutableList.copyOf(Arrays.asList(new PlayerLevel[0]));

Output (annotated and shortened):

public final void com.google.common.collect.ImmutableList.add(int,java.lang.Object)
----> public static com.google.common.collect.ImmutableList com.google.common.collect.ImmutableList.copyOf(java.lang.Iterable)
public static com.google.common.collect.ImmutableList com.google.common.collect.ImmutableList.copyOf(java.util.Iterator)
                 (lots of other methods)

java.lang.NoSuchMethodError: com.google.common.collect.ImmutableList.copyOf(Ljava/util/Collection;)Lcom/google/common/collect/ImmutableList;

Huh?

(If the logs are not clear enough, I get an error saying that ImmutableList.copyOf(List) is not a method, but by looping through all the methods I see there is a copyOf(Iterable), and List implements Iterable.)


Solution

  • Both methods are compatible at compile time. But runtime is another beast. I assume, that your code compiles against an older version of Google Collections but runs against a newer version.

    Edit: What happens in detail:

    Given the lines

    List<String tmpArray = Arrays.asList(new PlayerLevel[0]);
    ImmutableList.copyOf(tmpArray);
    

    the compiler starts to look for a suitable method in ImmutableList with the name copyOf and one parameter compatible to the static type List<String>. The version of the class visible to the compiler offers exactly one match:

    ImmutableList.copyOf(Collection<T> arg0);
    

    Please note, that the compiler is not interested in the actual type of tmpArray, only the static type (aka. "formal type") is considered.

    The compiler writes the signature of the selected method into the class file.

    At runtime the classloader / linker reads the class, finds the signature of the method

    ImmutableList.copyOf(Collection<T> arg0);
    

    and performs a lookup (not a search!) on ImmutableList for exactly the given signature. Compatibility does not matter here, that was the job of the compiler. You get the same results, if you use reflection like this:

    ImmutableList.class.method("copyOf", Collection.class);
    

    In both cases Java simply performs a lookup using exactly the given type. It does not perform a search like "return method(s) which can be called with the given type".

    In your case the runtime classpath and the compile time class are different. So the classloader / linker fails to perform the lookup.

    One step back

    This issue shows the different levels of compatibility:

    • Binary compatibility: Throw in a new jar and that's it.
    • Source compatibility: You have to compile your source but you don't have to change it.
    • Behavioural compatibility or Semantic compatibility: The client code must be changed.

    You can use these keywords to look around this site or on Google for more infos. A good reference for binary compatibility are the three parts of Evolving Java-based APIs.