Search code examples
javaeclipseannotationsnullablenon-nullable

Getting 'mismatching null constraints' error when implementing Map.entrySet (Java8, Eclipse)


I get the error...

The return type is incompatible with 'Set<Map.Entry<K,T>>' returned from Map<K,T>.entrySet() (mismatching null constraints)

...when implementing a Map and overriding Map.entrySet like this:

package org.abego.util;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
...    
public abstract class MyMap<K, T> implements Map<K, T> {
    private Map<K, T> map = new LinkedHashMap<>();

    @Override
    public Set<java.util.Map.Entry<K, T>> entrySet() {
        return map.entrySet();
    }
    ...
}

The package org.abego.util defines the default nullness to be @NonNull:

@org.eclipse.jdt.annotation.NonNullByDefault
package org.abego.util;

The only way I found to get rid of the error was to 'remove the default nullness annotation' for entrySet using an @NonNullByDefault({}) annotation:

package org.abego.util;
...
import org.eclipse.jdt.annotation.NonNullByDefault;

public abstract class MyMap<K, T> implements Map<K, T> {
    ...
    @Override
    @NonNullByDefault({})
    public Set<java.util.Map.Entry<K, T>> entrySet() {
        return map.entrySet();
    }
    ...
}

While this does work I am wondering if this is the correct way to fix the error.

(I am using Eclipse 4.5 (Mars) and jdk1.8.0_60.)


Solution

  • You are trying to override this method:

    Set<Map.Entry<K, V>> entrySet();
    

    with a method whose effective signature is

    @NonNull Set<@NonNull Map.Entry<K, V>> entrySet();
    

    This is an incompatible override as can be demonstrated via this caller:

    Set<Map.Entry<Foo,Bar>> entries = someMap.entrySet();
    entries.add(null);
    

    If someMap has type MyMap then the element type of entries is @NonNull, but the contract of java.util.Map.entrySet() promises a Set where null elements are tolerated (set has not specified nullness of its elements). Mixing both contracts (with @NonNull and unspecified) on the same object entries will break clients that assume non-null elements.

    Looking at the Javadoc of entrySet(), it should however be safe to assume that Map.entrySet() will always return a Set with non-null elements. Hence, the root of the problem is in the fact, that java.util.Map.entrySet() has no null annotations.

    Since Eclipse Mars, this can be overcome by way of external null annotations. You can tell the compiler that the return type of Map.entrySet() is in fact @NonNull Set<@NonNull Map.Entry<K,V>>, either by applying the Annotate command in the IDE, or by putting the following snippet into a file java/util/Map.eea inside a directory that has been configured as the location for external annotations for the JRE:

    class java/util/Map
    
    entrySet
     ()Ljava/util/Set<Ljava/util/Map$Entry<TK;TV;>;>;
     ()L1java/util/Set<L1java/util/Map$Entry<TK;TV;>;>;
    

    With such external annotations in place your program will be accepted by JDT's null analysis.