Possible defect with lazy loaded polymorphic entities

Wanted to run this specific scenario by the people here to determine if I need to open a defect ticket or if this is just an unfortunate consequence of polymorphic entities.

Let’s say you have two entities, and both entities have a ManyToOne to the same target entity, the only difference being that one is eager and one is lazy ex. TEST_EAGER and TEST_LAZY both reference REFERENCE. Now let’s say REFERENCE is polymorphic, and there is a join table called EXTENDED_REFERENCE. If you fetch an instance of TEST_LAZY, the reference on it will be a proxy object of type REFERENCE even though it references an EXTENDED_REFERENCE, which you can only obtain by unproxying. This is unfortunate, but just how it works.

The possible error case from this behavior, I have created this MRE for:

Tests 1-4 were just me verifying lazy behaviors with extended entities, but the 5th case is the really weird outcome. If you fetch an entity with a lazy reference, and then later in the transaction fetch an entity with an eager reference to the same target entity, it grabs the proxy from the entity manager and sticks that on the eager reference. Further, when logging sql, it does do the joined fetch when getting the eager object, but then uses the original proxy anyway. When you unproxy it, it does not go back to the database, it has the hydrated concrete type cached somewhere. This all came about because I had assumed that all EAGER joined ManyToOne references would always be of the correct polymorphic type, but this does not appear to be the case. In these cases, the only solution I can find is to check for proxies and unproxy it before attempting casts or instanceOf checks.

So is this the unfortunate but intended behavior, or should I open a defect ticket?

That’s just how this works. We process row by row, and if a row contains data for an association, we materialize that according to the association fetch type to either a proxy or the initialized real object.
Depending on the order of the rows (which can be influenced with order by), you might see something like this. Row 1 refers to Entity#2 through a lazy association. Row 2 refers to Entity#2 through an eager association. Then you will only ever see the proxy which was constructed for Row 1.

This is because Hibernate has to maintain identity of entity objects. There can only be one object in the persistence context for a table row. Every proxy refers to an actual entity object, and in the scenario I described, the entity object of the proxy would be initialized immediately, but we can’t replace proxy references with the actual object, as that would require a lot of overhead and might even violate some of the core principles of maintaining identity, because a setter method for an association could have already seen a proxy and stored it elsewhere.

So, this is by design. If you really need the actual object type, use Hibernate.unproxy(). I would recommend everyone to stop thinking about polymorphism being about the type of the object only. Use methods and override these in subtypes instead to do what you want. Checking with instanceof is just not going to work reliably.