In this tutorial, we will continue implementing an extension for the Criteria API using the Builder pattern and JPA Static Metamodel Generator. Please check the previous part: Improving your experience with Criteria API using Builder pattern and JPA Static Metamodel - Part I The full source code is available over on . Github In this part, we will improve the BaseQuery interface to allow you to use the predicates with a nested sub-path chain, providing us the ability to switch from this block of code: or/and List<Order> listOfOrders = criteriaApiHelper.select(Order.class) .equal(Order_.state, OrderStatus.Shipped) .like(Order_.customer, Customer_.address, Address_.addressLine, "11 Aleksandr Pushkin St") .equal(Order_.customer, Customer_.address, Address_.city, City_.name, "Tbilisi") .equal(Order_.customer, Customer_.address, Address_.city, City_.country, Country_.name, “Georgia”) .findAll(); To this: List<Order> listOfOrders = criteriaApiHelper.select(Order.class) .equal(Order_.status, OrderStatus.Shipped) .and(Order_.customer, Customer_.address, like(Address_.addressLine, "11 Aleksandr Pushkin St"), and(Address_.city, equal(City_.name, "Tbilisi"), equal(City_.country, Country_.name, "Georgia") ) ) .findAll(); 1. Implementation First of all, we need to define a way to build nested predicates. It’s quite problematic to split the builder chain if we need to specify multiple nested predicates within one predicate. To resolve this, we can delegate this nested predicates logic to the additional class that can hold its own predicates separately outside of the main builder chain: or/and or/and QueryPart public class QueryPart<R> extends BaseQueryImpl<R, QueryPart<R>> { @Override protected QueryPart<R> self() { return this; } } Next, let’s introduce new methods in the interface for the predicates, accepting varargs arguments: BaseQuery or/and QueryPart<R> public interface BaseQuery<R, Q extends BaseQuery<R, Q>> { … Q and(QueryPart<R>... partQueries); Q or(QueryPart<R>... partQueries); } Also, if we want to specify multiple predicates for one nested table, we can define a parent-child attributes chain to specify the nested relation only once: public interface BaseQuery<R, Q extends BaseQuery<R, Q>> { ... <P> Q and(SingularAttribute<R, P> attribute, QueryPart<P>... partQueries); <P1, P2> Q and(SingularAttribute<R, P1> attribute1, SingularAttribute<P1, P2> attribute2, QueryPart<P2>... partQueries); <P1, P2, P3> Q and(SingularAttribute<R, P1> attribute1, SingularAttribute<P1, P2> attribute2, SingularAttribute<P2, P3> attribute3, QueryPart<P3>... partQueries); <P> Q or(SingularAttribute<R, P> attribute, QueryPart<P>... partQueries); <P1, P2> Q or(SingularAttribute<R, P1> attribute1, SingularAttribute<P1, P2> attribute2, QueryPart<P2>... partQueries); <P1, P2, P3> Q or(SingularAttribute<R, P1> attribute1, SingularAttribute<P1, P2> attribute2, SingularAttribute<P2, P3> attribute3, QueryPart<P3>... partQueries); } Next, let’s implement these defined methods. To build all the -s, we should combine nested predicates using the method and pass the result to the method: QueryPart BaseQueryImpl#buildPredicates() CriteriaBuilder#and() Predicate[] predicates = Arrays.stream(partQueries) .map(partQuery -> cb.and(buildPredicates(CriteriaQuery, partQuery.predicates, CriteriaBuilder, Root))) .toArray(Predicate[]::new); But, as you may see, we can’t access the , and parameters. For this reason, we have provided the interface in the previous part: CriteriaQuery CriteriaBuilder Root QueryPredicate @FunctionalInterface public interface QueryPredicate<R> { Predicate apply(CommonAbstractCriteria criteria, CriteriaBuilder cb, Path<R> root); } public abstract class BaseQueryImpl<R, Q extends BaseQueryImpl<R, Q>> implements BaseQuery<R, Q> { protected final Collection<QueryPredicate<R>> predicates; ... } Therefore, to reach the , and we can just create an instance of : CriteriaQuery CriteriaBuilder Root QueryPredicate new QueryPredicate<R>() { @Override public Predicate apply(CommonAbstractCriteria criteria, CriteriaBuilder cb, Path<R> root) { Predicate[] predicates = Arrays.stream(partQueries) .map(partQuery -> cb.and(buildPredicates(criteria, partQuery.predicates, cb, root))) .toArray(Predicate[]::new); ... } }; Lastly, to build and return the instance, we should pass the predicates to the for the predicate and to the for the predicate accordingly: Predicate CriteriaBuilder#and() and CriteriaBuilder#or() or public abstract class BaseQueryImpl<R, Q extends BaseQueryImpl<R, Q>> implements BaseQuery<R, Q> { ... @SafeVarargs @Override public final Q and(QueryPart<R>... partQueries) { predicates.add((criteria, cb, root) -> { Predicate[] predicates = Arrays.stream(partQueries) .map(partQuery -> cb.and(buildPredicates(criteria, partQuery.predicates, cb, root))) .toArray(Predicate[]::new); return cb.and(predicates); }); return self(); } @SafeVarargs @Override public final Q or(QueryPart<R>... partQueries) { predicates.add((criteria, cb, root) -> { Predicate[] predicates = Arrays.stream(partQueries) .map(partQuery -> cb.or(buildPredicates(criteria, partQuery.predicates, cb, root))) .toArray(Predicate[]::new); return cb.or(predicates); }); return self(); } } Next, let’s implement methods with the nested parent-child relations chain accepting additional arguments. The only difference with these methods is that you should reach and pass the required instance using the provided attributes to the method: SingularAttribute Path BaseQueryImpl#buildPredicates() public abstract class BaseQueryImpl<R, Q extends BaseQueryImpl<R, Q>> implements BaseQuery<R, Q> { ... @SafeVarargs @Override public final <P> Q and(SingularAttribute<R, P> attribute, QueryPart<P>... partQueries) { predicates.add((criteria, cb, root) -> { Predicate[] predicates = Arrays.stream(partQueries) .map(partQuery -> cb.and(buildPredicates(criteria, partQuery.predicates, cb, root.get(attribute)))) .toArray(Predicate[]::new); return cb.and(predicates); }); return self(); } @SafeVarargs @Override public final <P1, P2> Q and(SingularAttribute<R, P1> attribute1, SingularAttribute<P1, P2> attribute2, QueryPart<P2>... partQueries) { predicates.add((criteria, cb, root) -> { Predicate[] predicates = Arrays.stream(partQueries) .map(partQuery -> cb.and(buildPredicates(criteria, partQuery.predicates, cb, root.get(attribute1).get(attribute2)))) .toArray(Predicate[]::new); return cb.and(predicates); }); return self(); } @SafeVarargs @Override public final <P1, P2, P3> Q and(SingularAttribute<R, P1> attribute1, SingularAttribute<P1, P2> attribute2, SingularAttribute<P2, P3> attribute3, QueryPart<P3>... partQueries) { predicates.add((criteria, cb, root) -> { Predicate[] predicates = Arrays.stream(partQueries) .map(partQuery -> cb.and(buildPredicates(criteria, partQuery.predicates, cb, root.get(attribute1).get(attribute2).get(attribute3)))) .toArray(Predicate[]::new); return cb.and(predicates); }); return self(); } @SafeVarargs @Override public final <P> Q or(SingularAttribute<R, P> attribute, QueryPart<P>... partQueries) { predicates.add((criteria, cb, root) -> { Predicate[] predicates = Arrays.stream(partQueries) .map(partQuery -> cb.or(buildPredicates(criteria, partQuery.predicates, cb, root.get(attribute)))).toArray(Predicate[]::new); return cb.or(predicates); }); return self(); } @SafeVarargs @Override public final <P1, P2> Q or(SingularAttribute<R, P1> attribute1, SingularAttribute<P1, P2> attribute2, QueryPart<P2>... partQueries) { predicates.add((criteria, cb, root) -> { Predicate[] predicates = Arrays.stream(partQueries) .map(partQuery -> cb.or(buildPredicates(criteria, partQuery.predicates, cb, root.get(attribute1).get(attribute2)))).toArray(Predicate[]::new); return cb.or(predicates); }); return self(); } @SafeVarargs @Override public final <P1, P2, P3> Q or(SingularAttribute<R, P1> attribute1, SingularAttribute<P1, P2> attribute2, SingularAttribute<P2, P3> attribute3, QueryPart<P3>... partQueries) { predicates.add((criteria, cb, root) -> { Predicate[] predicates = Arrays.stream(partQueries) .map(partQuery -> cb.or(buildPredicates(criteria, partQuery.predicates, cb, root.get(attribute1).get(attribute2).get(attribute3)))).toArray(Predicate[]::new); return cb.or(predicates); }); return self(); } } Now, we can finish the implementation by providing static methods in the , allowing us to create interfaces from any part of the code: CriteriaApiHelper QueryPart public class CriteriaApiHelper { ... public static <R, V> QueryPart<R> equal(SingularAttribute<R, V> attribute, V value) { return new QueryPart<R>() .equal(attribute, value); } public static <R, V> QueryPart<R> notEqual(SingularAttribute<R, V> attribute, V value) { return new QueryPart<R>() .notEqual(attribute, value); } public static <R> QueryPart<R> isTrue(SingularAttribute<R, Boolean> attribute) { return new QueryPart<R>() .isTrue(attribute); } public static <R> QueryPart<R> isFalse(SingularAttribute<R, Boolean> attribute) { return new QueryPart<R>() .isFalse(attribute); } public static <R, V> QueryPart<R> isNull(SingularAttribute<R, V> attribute) { return new QueryPart<R>() .isNull(attribute); } public static <R, V> QueryPart<R> isNotNull(SingularAttribute<R, V> attribute) { return new QueryPart<R>() .isNotNull(attribute); } public static <R, V extends Comparable<? super V>> QueryPart<R> greaterThan(SingularAttribute<R, V> attribute, V value) { return new QueryPart<R>() .greaterThan(attribute, value); } public static <R, V extends Comparable<? super V>> QueryPart<R> lessThan(SingularAttribute<R, V> attribute, V value) { return new QueryPart<R>() .lessThan(attribute, value); } public static <R> QueryPart<R> like(SingularAttribute<R, String> attribute, String value) { return new QueryPart<R>() .like(attribute, value); } @SafeVarargs public static <R> QueryPart<R> and(QueryPart<R>... partQueries) { return new QueryPart<R>() .and(partQueries); } @SafeVarargs public static <R> QueryPart<R> or(QueryPart<R>... partQueries) { return new QueryPart<R>() .or(partQueries); } } 2 Wrapping up In this part, we slightly improved the extension for the Criteria API. Now we can perform predicates with the common parent-child relation: or/and import static org.example.criteria.api.helper.CriteriaApiHelper.or; import static org.example.criteria.api.helper.CriteriaApiHelper.equal; import static org.example.criteria.api.helper.CriteriaApiHelper.like; List<Order> listOfOrders = criteriaApiHelper.select(Order.class) .equal(Order_.status, OrderStatus.Shipped) .and(Order_.customer, Customer_.address, like(Address_.addressLine, "11 Aleksandr Pushkin St"), or(Address_.city, equal(City_.name, "Tbilisi"), equal(City_.country, Country_.name, "Georgia") ) ) .findAll();