Except they're not, as I can do Integer x = null, but not int x = null. So an Integer is forced to occupy more memory, for very very unclear reasons. And this is also deeply weird - there is no other (mainstream?) language that allows null value types.
Yes and no, because in Java we have runtime types and compile-time types. The frontend compiler will treat these types as having different defaults on nullability, but they'll compile down to the same representation (when appropriate). I.e. if the compiler sees that some Integer variable is never null, it will compile down to the same thing it would if it were declared an int.
You're right, however, that on the heap, until the language adds nullability information, the compiler cannot generally know that an Integer will never be null (unless it's a final field), so it's likely that, unlike on the stack, you'll get a different representation.
That goal is an ideal and can't be reached perfectly. Converting a type to a value type will break clients that synchronize on them, or rely on identity for some reason. But such cases are rare, and can be weighed up on an individual basis when making the decision about whether to do it. Storing things in a nullable variable on the other hand is very common and changing the rules to prevent it would make every such change a source incompatible breaking change.