Skip to content

Instantly share code, notes, and snippets.

@erikdw
Forked from AlainODea/HelloCovariance.java
Last active October 6, 2016 09:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save erikdw/95311c7873553f7fa98fd9548d1b916f to your computer and use it in GitHub Desktop.
Save erikdw/95311c7873553f7fa98fd9548d1b916f to your computer and use it in GitHub Desktop.
Exception in thread "main" java.lang.NoSuchMethodError: java.util.concurrent.ConcurrentHashMap.keySet()Ljava/util/concurrent/ConcurrentHashMap$KeySetView;

Interaction of Covariance and Java Cross-compile

Here is a Java class with a compatibility problem:

HelloCovariance.java:

import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

public class HelloCovariance {
  public static void main(String[] args) {
    ConcurrentHashMap<String, String> properties = new ConcurrentHashMap<>();
    Set<String> keySet = properties.keySet();
  }
}

Here is a session log that seems implausible:

$ /usr/lib/jvm/java-8-oracle/bin/javac -source 1.7 -target 1.7 HelloCovariance.java 
warning: [options] bootstrap class path not set in conjunction with -source 1.7
1 warning
$ /usr/lib/jvm/java-1.7.0-openjdk-amd64/bin/java HelloCovariance 
Exception in thread "main" java.lang.NoSuchMethodError: java.util.concurrent.ConcurrentHashMap.keySet()Ljava/util/concurrent/ConcurrentHashMap$KeySetView;
	at HelloCovariance.main(HelloCovariance.java:7)

Why does this NoSuchMethodError happen?

Compare the JavaDoc for ConcurrentHashMap#keySet() in Java 1.7 and 1.8:

Notably the Java 1.7 ConcurrentHashMap#keySet() returns a Set<K> while the 1.8 ConcurrentHashMap#keySet() returns a `ConcurrentHashMap.KeySetView<K,V>``.

How can this work? It turns out that a overriding method is allowed to return a sub-type of the parent methods return type. This is due to covariance. This is useful in certain cases where you know the concrete types and want to avail of them somehow.

You'll notice that there is a warning about bootstrap classpath. It turns out those bootstrap classpath warnings have a lot of merit. The safest fix is clearly to compile with a bootstrap classpath.

Failing that you can avoid the unnecessary use of a concrete type for a variable here.

Using the general Map interface in place of the concrete ConcurrentHashMap type here side-steps the coupling to the Java 8 return type and will allow this code to be compiled with Java 8 and run on Java 7.

HelloSidestep.java:

import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

public class HelloSidestep {
  public static void main(String[] args) {
    Map<String, String> properties = new ConcurrentHashMap<>();
    Set<String> keySet = properties.keySet();
  }
}
$ /usr/lib/jvm/java-8-oracle/bin/javac -source 1.7 -target 1.7 HelloCovariance.java 
warning: [options] bootstrap class path not set in conjunction with -source 1.7
1 warning
$ /usr/lib/jvm/java-1.7.0-openjdk-amd64/bin/java HelloCovariance 

Hey look! No crash. This is not really the right fix, and you will be playing whack-a-mole with similar bugs in a large system. The correct and recommended fix is to use a bootstrap classpath.

$ /usr/lib/jvm/java-8-oracle/bin/javac -source 1.7 -target 1.7 HelloCovariance.java -bootclasspath /usr/lib/jvm/java-1.7.0-openjdk-amd64/jre/lib/rt.jar
$ /usr/lib/jvm/java-1.7.0-openjdk-amd64/bin/java HelloCovariance 

Now we get no warnings and no crash.

This is not a new problem, but it seems like there is still a lot of confusion around it.

Here are some useful resources to consider:

#!/usr/bin/env bash
# Tested on Ubuntu with WebUpd8 install of Oracle JDK 8
/usr/lib/jvm/java-8-oracle/bin/javac -source 1.7 -target 1.7 HelloCovariance.java
/usr/lib/jvm/java-1.7.0-openjdk-amd64/bin/java HelloCovariance
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class HelloCovariance {
public static void main(String[] args) {
ConcurrentHashMap<String, String> properties = new ConcurrentHashMap<>();
Set<String> keySet = properties.keySet();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment