I am thinking something like this (but this is not even a draft, it's a brainstorm, the final result needs to be rewritten from scratch):
In order of priority:
1) Minimize the number of join, ifExists and ifNotExists usages. Each one combines two constraint streams which is an expensive operation.
2) Minimize the usage of filter() after (and filtering() during) the use of join, ifExists or ifNotExists. Instead, maximimize the usage of Joiners equals/lessThan/etc (which employ hashing and indexing) to avoid creating an expensive cartesian product.
3) put the most reducing of the Joiners first. So if you have 100 persons and 2 dogs, do a.join(B, equal(getPerson), equal(getDog)) and not a.join(B, equal(getDog)), equal(getPerson)). // todo add motivation
However, don't forget:
// todo something that says don't do when(Task).filter(task -> task.getOtherTask().isFoo())