1) "A string is a subtype of AnyRef".isInstanceOf[AnyRef] returns true . This is also true for sub- AnyRef other than Null . The only irregularity there, as you said, corresponds to Java.
2) If B is a subtype of A , that is, B <: A , you can always override the method:
def foo: A = ...
in
override def foo: B = ...
This is called return type refinement and is always allowed. Since Nothing is a subtype of any other type ( Nothing <: A for all A ), you can always specify your return type to Nothing (for example, by throwing an exception in the method body). This is a fairly regular property. The covariance of the return type is not directly related to the annotations of variations in the type parameters .
3) The remaining questions are well covered.
4) This is because the Null type does not exist in the Java runtime. I think if you wanted to emulate this, you could create your own instanceOf method - you first need to check if the argument is Null , otherwise do the usual isInstanceOf check.
There are other violations, yes. See For example: If Int cannot be null, what does the value null.asInstanceOf [Int] mean?
Arrays are another example where you can pay uniformity to shared arrays using boxing / unpacking or instanceOf checks at runtime. new Array[Any] converted to an array of objects - storing an integer in the array will lead to its boxing. Whenever you use Array[T] , where T does not have an upper bound, the array will have a pattern mapped to the correct runtime array type each time the element is indexed.
To better understand how you might be surprised, it is useful to think about how these constructions are converted to JVMs, where there is the concept of primitive and reference types, boxing / unpacking, and different classes of classes.