When using the Criteria API to find entities based on their relationship to a third level entity, Hibernate will hydrate the LAZY collection with incomplete data, except if there are conditions on the second level entity.
In the attached test, the following entities are defined:
A --(many to many)--> B --(many to many)--> C
The test then defines a Criteria to list all As related to one specific C using JoinType.LEFT_OUTER_JOIN.
If there's no condition on B, the list of Bs within the As will be initialized with incomplete data, resembling to a wrong EAGER initialization.
When a dummy condition is added on B (list of Cs not empty), then the list of Bs within the As will behave as expected and will be fully initialized only when accessed.
If JoinType.INNER_JOIN is used, the problem does not seem to happen.
THis behavior doesn't seem to match with https://docs.jboss.org/hibernate/orm/4.3/manual/en-US/html/ch17.html#querycriteria-associations :
"The kittens collections held by the Cat instances returned by the previous two queries are not pre-filtered by the criteria. If you want to retrieve just the kittens that match the criteria, you must use a ResultTransformer."
I am not surprised about inconsistencies. really should not have been applied. Also, see HHH-6877.
The legacy org.hibernate.Criteria API is considered deprecated and we are no longer making changes to it: http://docs.jboss.org/hibernate/orm/4.3/devguide/en-US/html_single/#d5e5506
To whoever falls into this situation and can't just give up on the Criteria API:
Use INNER_JOIN wherever possible instead of LEFT_OUTER_JOIN if joining on a collection.
If LEFT_OUTER_JOIN must be used on collection, be sure to have a (dummy) restriction on the collection, otherwise it may get loaded with inconsistent elements. A restriction blocks the collection from being initialized with the results of the sub query. The collection will then be loaded as expected when used by the code (lazy initialization).
Example of "dummy" restriction: Restrictions.isNotEmpty( collection ). Rationale: if you're left-joining on it to get to a child element and filter on it, the matched collection can't be empty anyway.