Using a null reference is not a behavior ether the Java or Scala type systems promise to prevent, just as Rust's type system does not promise to prevent panic!.
Java's type system is unsound if, say, without casting or other "unsafe" operations, we could have a compile-time Integer that is actually a runtime String.
(2) It's all relative. Most languages allow lots of invalid operations on data.
What if I'm expecting my variable to contain a non-empty list and I try to do non-empty list things to it and fail? Dependently-typed languages like Idris say everyone else is moving the goal posts by accepting this behavior in their type systems.
Imagine suddenly if your setting was changed, and e.g. you were not running Java code on the JVM, but directly compiled to native code. It's very well defined what happens when you try to use a `String` but it's actually a `null`, even at the bare metal level - you get a NullPointerException. Easy. Catch the exception somewhere, or don't, it's all very straightforward.
And despite what you said in your post -- to the contrary, a program which dereferences `null` is completely well defined and totally meaningful! It is not nonsense from a semantics point of view. Does a program which dereferences null do anything useful, all on its own? No. But it absolutely is a program that has meaning and you can describe what will happen by looking at it (roughly speaking), because its behavior is fully defined.
What happens at the bare-metal level when you try to use this Unsound module to convert an `Integer` to a `String`? Well... Bad things will probably happen, up-to and possibly including things like memory corruption or outright termination, depending on how the implementation works. Maybe there is an optimization so small `Integer`s are represented with a different object layout than a large `String` is, at runtime. The runtime system will likely clobber itself if it tried to directly coerce these two different objects, with different sizes, fields, layouts, etc -- because like most typed languages, it would likely rely on the object type to describe memory layout (for things like the GC, so it knows where to find pointers). Classic memory corruption scenario.
The JVM itself is not broken by this problem, so it is "correctly" handled for most practical purposes, but only by that coincidence that the JVM retains type safety here. Despite that, there is still no true "meaning" to this program. It is, like the prior program, useless -- but the prior program was meaningful. This program is not.
Similarly, it's necessary to understand these kinds of corner cases if we want to do things like formalize compilers, semantics, and design find better points in the design space that can ameliorate these problems (either by tooling, language revisions/changes, or entirely new languages).
For most end users, you're basically right, things like this will "only" manifest as a runtime error, and a runtime error is a runtime error is a runtime error. But the paper is about the actual semantics of Java the language, which is a much more broad topic, as opposed to the fact that yes, there's a new way to get a runtime exception.
Yes it does. It produces an exception, which contains a stack trace. I've actually used that, when I had a program that was reaching some code, and I couldn't figure out what the path it took to get there. It was easier to create an exception, catch it, and print the stack trace than to fire up a debugger and set a breakpoint.