By that standard, C and C++ are much worse, since they offer no runtime encapsulation at all, and have much worse and more subtle multithreaded errors (e.g. Java at least guarantees that all native word sized reads/writes are atomic, if I recall correctly). C++ doesn't even guarantee that a reference can't be null, or worse, deallocated before it is dereferenced. They allow you to
specify that a field is of some type and
shouldn't be null, which is nice, but they don't enforce that in any way, they just call any code path that violates it UB.
For example, this is code that any C or C++ compiler will happily run and do something:
struct Bar {
int b;
};
struct Foo {
struct Bar bar;
} foo;
strcpy((char*)(&foo), "ABC");
Or in relation to null C++ references:
int& foo(int* p) {
return *p;
}
int &r = foo(nullptr); //UB, but in practice will likely result in a null reference at runtime
Similarly, accessing an object from multiple threads without synchronization means its value is not fully defined in Java. Unlike C or C++, it is at least known to be a Java type, not a memory corruption vulnerability.