BTW, Java does have implementation-defined or unspecified behavior (for instance, using the same objects from different threads without proper coordination), but there's much less of it and it's easier to avoid.
BTW, Java does have implementation-defined or unspecified behavior (for instance, using the same objects from different threads without proper coordination), but there's much less of it and it's easier to avoid.
That said, AFAIK language-level invariants in Java (and Python, etc.) cannot be violated by the lack of thread safety; meaning, you can still inspect every live object and see its state. Program-level invariants might go to heck, for sure. So what I should have said is more along the lines of "memory safety" than "lack of undefined behavior"; I'm not sure what the shortest and most precise way to put it is, though. (It does not quite end with memory safety but it doesn't quite get to lack of undefined behavior, either.)
A lot of work has been put into tools and our general ability to write better code (although whether we do so or not is a different issue).