In C#, List<T> and List<U> follows the same assignment rules as T and U, and at runtime are represented by distinct types. That means that going from List<T> to object to List<U> causes a runtime error at the point of casting.
In Java, every generic type is erased to object at runtime, so the runtime type is just List, and you could cast List<T> to object to List<U> and only get an error later, when you try calling U methods on the contents of the list.
(Yes in C# List is a concrete vector type and in Java it is a random-access collection interface, but that is not relevant here)
The delayed error happens only when you ignore unchecked warnings, which would have been compiler errors if not for backward compatibility. One can turn them into errors with `-Werror`.
The type erasure has occasional benefits, like allowing objects that are polymorphic in their type argument when that’s still safe semantically (a simple example being emptyList() and emptySet()), where the type system isn’t expressive enough to otherwise allow it. This is a bit like the “unsafe” escape in other languages.
if T and U don't have the same erasure the compiler will forbid the cast
if they do the compiler will warn you that an List<T> to List<U> cast is naughty
but in that case the only methods you can call on it are that of the erased type anyway
in practice I don't think I've ever seen a bug as a result of this type of erasure (and I've probably worked with at least several million lines of Java)
To allow backward compatibility Java introduced Generics with type erasure, which in short means they only exist at compile-time, not at runtime (there are some hacks around that, which various devs have used with great success to still get the information). That is another reason to just start with Generics from the beginning if you design a new language, so you won't have compatibility problems when you introduce them. It's not like Generics were a controversial feature when Go came out.
C#, which is often cited as an example for "generics done right" chose another path, which allowed generics at runtime - they made a hard break and just threw backward compatibility out of the window iirc. The reason Javas designers didn't do that is not only introduced generics far later in its lifecycle, but Java also has always followed the hard rule that breaking backward compatibility is something which should only ever used as a last resort and never between two versions directly following each other.
IIRC Arthur Van Hoff, one of the original Java developers, actually advocated for the inclusion of generics in the initial version, but it was dropped due to time constraints. It’s one of those features that a statically types language will always regret to not include from the beginning.
The "threw backward compatibility out of the window" happened in .NETFW 2.0, in 2005 (it did not, non-generic code that targets the pre-2.0 spec would work even today, the SDK is dead long ago but copied verbatim it would just run).
No, Java generics are basically syntactic sugar over casts, which is why types are erased at runtime when you're trying to debug. Performance also isn't as good as for C# generics since the Java approach limits optimizations.
As someone who works with Java all the time I'm the first to admit that something like TypeToken in GSON or comparable things in other libraries is not the greatest of things. I've also more than once wished I could to if(xy instanceof List<Something>), which you cannot do in Java. Can you work around it? Sure. Do I understand why Java has it? Yes. But "minor" .. no, it's not so minor in my experience.
In C#, List<T> and List<U> follows the same assignment rules as T and U, and at runtime are represented by distinct types. That means that going from List<T> to object to List<U> causes a runtime error at the point of casting.
In Java, every generic type is erased to object at runtime, so the runtime type is just List, and you could cast List<T> to object to List<U> and only get an error later, when you try calling U methods on the contents of the list.
(Yes in C# List is a concrete vector type and in Java it is a random-access collection interface, but that is not relevant here)