Reference Stability is Sometimes Necessary

When a boxed value is being used as a simple reference, sometimes reference identity must be preserved.
This means there must be defined scopes where reboxing is not allowed.

CAS of value into Object reference field
AtomicReference<Object> ref = new AtomicReference<Object>();
Object c1 = Complex.valueOf(1, 0);
Object c2 = Complex.valueOf(0, 1);
ref.set(c1);
boolean ok;
ok = ref.compareAndSet(c1, c2);
assert(ok);

The assertion can fail if reboxing of c1 is allowed in this code.

Non-final fields may receive value and not-value objects

If there are sources of both value and non-value objects of a given type (the compatibility story assumes that this is the case) then non-final fields may have both stored in them, embedded within some other object that has object identity and thus is somewhat difficult to resize outside of a garbage collection.

Arrays may receive both value and non-value elements

In the same way that non-final fields may have both value and non-value objects of the same type stored in them at different times, arrays may have value and non-value objects stored in them. This interferes with allocation of arrays of immediate data; if any non-value object of the element type is stored in the array, it must be reallocated (if the element value's size in bytes is different from a reference's size in bytes) to accommodate the stored reference.

Lack of finalizers interferes with some desirable use cases

Unsafe references to packed storage can be wrapped up in safe object handles, and this is one implementation technique for native-code-friendly "arrays". The unsafe-allocated data can be managed in a finalizer for the safe handle; if there are no references to the safe handle, then it is safe to deallocate the packed storage. It would be useful (perhaps more efficient) to designate the safe handles as value objects so that the overhead of heap allocation for new safe handles can be avoided for what would be just "pointer arithmetic" in other languages. One idiomatic way around this would be to use a non-value memory management object that had a finalizer, and include a reference to the memory management object in the safe object handles in addition to the unsafe reference. However, since the memory management object will go unused, dead code elimination might not actually include it in the generated code, which would undermine the intent of the idiom.

Legacy code might expect reference semantics

Legacy code may make use of types that are later used for value objects, but expect them to have reference semantics.

class Legacy extends Thread {

  private final Float teamLock;
  public Legacy(Float team_lock) {
     teamLock = team_lock;
  }
  
  public void run() {
     ...
     synchronized(teamLock) {
        ...
     }
     ...
  }
}

Such code would not compile in the new value-objects, but the class files could still be referenced from either old or new code. The call to Float.valueOf will run the new-code factory for Float value objects, but it will be seen by old code.

...
Float team_lock = Float.valueOf(42.0);
LegacyThread t1 = new LegacyThread(team_lock);
LegacyThread t2 = new LegacyThread(team_lock);
...