> You can get CCEs without explicit casts or compiler bugs though
Using raw types is cheating. It's essentially using a different language and, as you say, the compiler will tell you that you are.
> However, if you obfuscate it a little javac doesn't know about it anymore.
It's a bug (rather, a few bugs): http://wouter.coekaerts.be/2018/java-type-system-broken (I assume that's where you got the example from)
You shouldn't get a CCE without explicit casts, and if you do, that's a (soundness) bug in the Java compiler and/or spec. Those bugs aren't always fixed quickly when they're not very important in practice (if it's determined that it's hard to hit them by mistake, which is why it's taken 15 years to find them).
> Would expect this line to blow up either at compile-time or at runtime with reified generics
Reified generics have no impact at compile time. Everything a language with reified generics knows at compile-time, a language with erased generics also knows. However, a runtime with reified generics and a similar compiler bug would, indeed, throw at this line at runtime.
> As for weirdness, I can't think of any other language with generics that preserves runtime type information without a way of preserving that for generics as well.
There are good reasons for doing it the Java way: so that you don't bake a language's particular variance strategy into the VM. There are also good reasons for doing it the C#: it's slightly more convenient for that one language. I can't think of any other language with variance and a runtime type system that reifies generics for reference types. When all you have are two instances, it's hard to say which of them is weird.
> If the CIL separates them then the downcast trick works. If it doesn't the trick doesn't work.
I don't know CIL, but looking at how languages behave on .NET to interop, I wouldn't think that can work. It would totally break the safety of the runtime type system if you could cast without a typecheck.
> I guess the only difference is that you still have storage of the types so you can do runtime reflection.
BTW, you can reflect on generic type arguments in Java, as well (https://docs.oracle.com/en/java/javase/13/docs/api/java.base...). When you reflect on a method that returns a List<String>, it will tell you it returns List<String>, but you can't get such a Type from an instance of that list; you can only get a Class.