That is, two bugs in the language specification that allow a program to compile without warnings but to produce a ClassCastException at runtime on a line with no explicit cast.

This subtle interaction between generics and inner classes pales in comparison to...

The Java language specification lacks rules about casts to inner classes. Commonly used versions of javac happen to allow us to cast to Inner — that is, to cast to Outer<E>.Inner — without a warning. The more honest version of the cast, (Outer<E>.Inner) , ought likewise to be permitted with a warning; it's rejected outright.

Code

import java.util.HashSet; import static java.util.Collections.singleton; class Printer { static <E> void print(Iterable<E> input) { for (E e : input) { System.out.println("Unsorted " + e); } } } class SortedPrinter extends Printer { static <E extends Comparable<? super E>> void print(Iterable<E> input) { for (E e : input) { System.out.println("Sorted " + e); } } } class Caller { public static void main(String[] args) { SortedPrinter.print(singleton(1)); SortedPrinter.print(singleton(null)); SortedPrinter.print(singleton(new HashSet<Object>())); } }

Output

Sorted 1 Sorted null Exception in thread "main" java.lang.ClassCastException: java.util.HashSet cannot be cast to java.lang.Comparable at SortedPrinter.print(Caller.java:14) at Caller.main(Caller.java:24)

Explanation

You need two obscure pieces of knowledge to understand this one: At runtime, methods are identified by these four pieces of information: return type

class name

method name

argument types (Note the absence of anything related to type parameters.) Indirect references to static methods are compiled oddly: class Base { static void foo() {} } class Derived extends Base { } The bytecode for a call to Derived.foo() is " void Derived.foo() ," not " void Base.foo() ." The VM knows to look for the method first in Derived and then in Base . So: The compiler can see that Collection<HashSet<Object>> does not implement Comparable . Thus, it chooses the more general "inherited" version <E> print(Iterable<E>) over the restricted version <E extends Comparable<? super E>> print(Iterable<E>) . Nevertheless, per point #2 above, the bytecode it produces still reads " void SortedPrinter.print(Iterable) ." The VM sees " void SortedPrinter.print(Iterable) " and begins its search for void print(Iterable) in SortedPrinter . SortedPrinter has exactly such a method. The fact that it requires its input to be Comparable is unavailable at runtime. The VM chooses that method. Thus, all three " SortedPrinter.print() " calls end up really calling SortedPrinter.print() , even though the compiler/IDE believes it is compiling a call to Printer.print() in the HashSet case. At runtime, SortedPrinter.print() implicitly casts each element of its input to Comparable (" for (E e : input) "), and the failure to cast HashSet gives us a ClassCastException .

More Fun

This is a hole in the type system, but the fun doesn't stop there. By adding method X, I can cause a call to method Y to become a call to method Z: import java.util.HashSet; import static java.util.Collections.singleton; class Base { static <E> Object count(Iterable<E> input) { System.out.println("Base"); int count = 0; for (E e : input) { count++; } return count; } } class Sub extends Base { /* static <E> Integer count(Iterable<E> input) { System.out.println("Sub"); int count = 0; for (E e : input) { count++; } return count; }*/ } class Subsub extends Sub { static <E extends Comparable<? super E>> Integer count(Iterable<E> input) { System.out.println("Subsub"); int count = 0; for (E e : input) { count++; } return count; } } class Caller2 { public static void main(String[] args) { Subsub.count(singleton(1)); Subsub.count(singleton(null)); Subsub.count(singleton(new HashSet<Object>())); } } With Sub.count() commented out, we get: Subsub Base Base With Sub.count() restored, we get: Subsub Subsub Subsub <the ClassCastException> Notice that in no case is Sub.count() itself called. Its mere presence affects the behavior of the program. Why? The existence of Sub.count() makes the compiler write " Integer Subsub.count() " instead of " Object Subsub.count() ." At runtime, the former resolves to Subsub.count() ; the latter, to Base.count() . If you do want Sub.count() to be called, you must change its return type to Object . Then we get: Subsub Sub Sub Changing the method's return type again changes which method is called. The first method the compiler finds when looking for an appropriate Subsub.count() is still Sub.count() , but now it writes " Object Subsub.count() " instead of " Integer Subsub.count() ." At runtime, the former resolves to the appropriate method.

Lessons