You are confusing topics. There is joining. And there is fetching. Two different things. You want to join; great, fine, no problem. Where you are running into trouble is that you want to join fetch. Joining, just like in SQL, is the process of expanding the available attribute space. join fetch also adds that association to the SQL select clause. Its easier to see in HQL:
select o from Order o join o.lines l where l.item = :theX
which is very different from
select o from Order o join fetch o.lines l where l.item = :theX
In the first example, the SQL select contains only the columns for Order. But in the second example, the SQL select contains the columns for (1) Order, (2) Order.lines, (3) OrderLine. So in the second case, if you filter the results, you filter the collection as well. That's HowItShouldBe.
Yes, there are limitations to Criteria in this regard. You see its history peeking through the corners a bit in this distinction being made internally between JoinType#LEFT_OUTER_JOIN and JoinType#INNER_JOIN. The JoinType here is used to indicate 2 different things. First, it indicates the type of physical join to use in the SQL. Secondly it indicates fetching if JoinType == LEFT_OUTER_JOIN. These should be tracked separately, but again this is a legacy issue.
Really, what you should be doing is to either remove the join fetching from the mappings (recommended anyway) or use HQL here since it does not honor mapping-defined join fetching.