It's not necessarily a matter of "do these things behave the same at runtime on the JVM I ran my program on", it's a matter of semantics -- what these programs mean -- and that actually does have a direct impact on what happens at runtime, if you widen your horizons a little. Focus on the cause, not the effect. It's not really moving the goalposts, considering nobody was talking about dereferencing a `null` String in the first place, least of all, the paper itself. `null` is crucial to their argument, but injecting your own impressions about how "it's nothing new, because I had a runtime error before, take that, dumb dumb academics" is mostly irrelevant posturing, it's not what the paper is about.

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.