Rules of overriding with respect to generics

Here’s a minimalistic example

import java.util.List;

public class SomeClass {
    interface SuperType {

    static class SubType implements SuperType {

    interface SuperTypeProcessor {
        void processSubtypesOfSupertype(List<? extends SuperType> superTypes);

        List<? extends SuperType> getSubtypesOfSupertype();

    static class ConcreteSuperTypeProcessor implements SuperTypeProcessor {
        // doesn't override!
        public void processSubtypesOfSupertype(List<SubType> superTypes) {

        // DOES override!
        public List<SubType> getSubtypesOfSupertype() {
            return null;

I thought List<SubType> is always a subtype of List<? extends SuperType>. Maybe, it is, but I need to revisit the rules of overriding?

Let’s see what the documentation says

An instance method m1, declared in class C, overrides another instance method m2, declared in class A iff all of the following are true:

C is a subclass of A. :white_check_mark:

The signature of m1 is a subsignature (§8.4.2) of the signature of m2. :thinking:


m2 is public, protected, or declared with default access in the same package as C, or :white_check_mark:

m1 overrides a method m3 (m3 distinct from m1, m3 distinct from m2), such that m3 overrides m2.

What is a subsignature?

The signature of a method m1 is a subsignature of the signature of a method m2 if either:

m2 has the same signature as m1, or :x:

the signature of m1 is the same as the erasure (§4.6) of the signature of m2. :thinking:

What is the erasure of a signature?

Type erasure is a mapping from types (possibly including parameterized types and type variables) to types (that are never parameterized types or type variables). We write |T| for the erasure of type T. The erasure mapping is defined as follows:

The erasure of a parameterized type (§4.5) G<T1,…,Tn> is |G|.

The erasure of a nested type T.C is |T|.C.

The erasure of an array type T is |T|.

The erasure of a type variable (§4.4) is the erasure of its leftmost bound. :thinking:

The erasure of every other type is the type itself.

It surely must mean

  • void processSubtypesOfSupertype(List<? extends SuperType>) is erased into void processSubtypesOfSupertype(List<SuperType>) and
  • List<? extends SuperType> getSubtypesOfSupertype() is erased into List<SuperType> getSubtypesOfSupertype()

If so, then why is List<SubType> getSubtypesOfSupertype() a valid override? List<SubType> getSubtypesOfSupertype() is, by that definition, as I understood it, not a subsignature of (type-erased) List<SuperType> getSubtypesOfSupertype()

What am I missing here?