Currently, predicates/sorts/etc. can be re-used by calling .toPredicate()/toSort()/etc. on the terminal context in the DSL. The user gets a SearchPredicate/SearchSort/etc. instance that can be reused in multiple queries, even in different threads.
But, there are rough edges.
First, regarding the API, the name .toPredicate()/toSort()/etc. do not make it clear that you only need to call these methods if you need re-usable objects. In any other case, just passing the terminal DSL context to other methods of the DSL should work fine. On the other hand, if you're using the non-lambda syntax, you probably want to call these methods, even if you don't reuse the objects: the object need to be stored in variables, and having to write down the type of the DSL contexts in your code will be painful.
Second, regarding the implementation:
SearchPredicate/SearchSort/etc. are not actually immutable. They internally store the builders that were used by the DSL, so if you keep a reference to the DSL contexts around, you should be able to change the builders, and thus the SearchPredicate/SearchSort/etc. Maybe we should somehow "freeze" the builders when we build the SearchPredicate/SearchSort/etc., so that trying to call a method on the DSL after that will trigger an exception?
When reusing a SearchPredicate/SearchSort/etc., we do not check that the search target is compatible with the one the objects were originally created for, beyond the usual technology check (Lucene/Elasticsearch). For example if you create a SearchPredicate while targeting the index book, then you re-use it while targeting the index user, something will eventually go wrong, but we won't detect it. Maybe we should? Ideally, when a SearchPredicate/SearchSort/etc. was created with a target A, and is reused in a target B, we should make sure that B is a subset of A. If we do that, worst case, the fields referenced in the predicate/sort/etc. do not exist, but if they do we are sure they are compatible.
The DSL contexts can also be reused. They shouldn't be, because they reference objects that are no longer useful once the predicate/sort/etc. is built and should be garbage-collected, but everything will work if you just store the DSL contexts somewhere and re-use them. Maybe we should prevent that somehow?