Writing a clean Predicate Builder I have an inner class for filtering:
private class RegSpecification implements Specification&lt;Reg&gt; {

    private final transient RegFilterDTO filter;
    private transient Predicate predicate;
    private transient CriteriaBuilder cb;

    public RegSpecification(RegFilterDTO filter) {
        this.filter = filter;
    }

    @Override
    public Predicate toPredicate(Root&lt;Reg&gt; root, CriteriaQuery&lt;?&gt; cq, CriteriaBuilder cb) {
        // this.cb = cb;
        this.predicate = cb.conjunction();

        Join&lt;Reg, Acct&gt; userJoin = root.join(acct, JoinType.INNER);

        predicate = eqEnumVal(predicate, cb, root.get(regType), RegType.class, filter.getRegType());
        predicate = eq(predicate, cb, userJoin.get(Acct_.id), filter.getAcctId());
        predicate = like(predicate, cb, root.get(regNumber), filter.getRegNumber());

        return predicate;
    }
}

And I made a helper class, to be used by other class as well who implements filtering by Specification (methods are public since some services are in other packages):
public class PredicateBuilder {

    public static Predicate like(Predicate predicate, CriteriaBuilder cb, Path&lt;String&gt; path, String value) {
        if (StringUtils.isNotEmpty(value)) {
            return cb.and(predicate, cb.like(cb.lower(path), &quot;%&quot; + value.toLowerCase() + &quot;%&quot;));
        }
        return predicate;
    }

    public static &lt;T&gt; Predicate eq(Predicate predicate, CriteriaBuilder cb, Path&lt;T&gt; path, T value) {
        if (value != null) {
            return cb.and(predicate, cb.equal(path, value));
        }
        return predicate;
    }

    public static &lt;T extends Enum&lt;T&gt;&gt; Predicate eqEnumVal(Predicate predicate, CriteriaBuilder cb,
                                                          Path&lt;T&gt; path, Class&lt;T&gt; enumType, String name) {
        if (StringUtils.isNotEmpty(name)) {
            return eq(predicate, cb, path, Enum.valueOf(enumType, name));
        }
        return predicate;
    }

}

Question is
Can I use private local variables in my RegSpecification so I won't have to use predicate = ... every time I call the helper class and some generic method to pass the predicate and cb to the helper class (so I won't have to pass the predicate and cb in my helper class methods)?
Where I could just do something like:
PredicateBuilder.with(predicate, cb)
   ...
   like(userJoin.get(Acct_.id), filter.getAcctId());
   eq(userJoin.get(Acct_.id), filter.getAcctId());
   like(root.get(regNumber), filter.getRegNumber());
   ...
.build();

in my toPredicate method.
Or any suggestions to make the code cleaner?
(Should I also annotate the helper class with @Component?)
You could follow the below approach:
public class PredicateBuilder {

    private Predicate predicate;
    private CriteriaBuilder cb;
    private Runnable[] tasks;
    
    private static final PredicateBuilder instance = new PredicateBuilder();
    private PredicateBuilder(){
    }
    
    public static PredicateBuilder with(Predicate p, CriteriaBuilder c) {
        instance.predicate = p;
        instance.cb = c;
        return instance;
    }

    public PredicateBuilder tasks(Runnable... tasks) {
        instance.tasks = tasks;
        return instance;
    }

    public Predicate build() {
        Arrays.stream(instance.tasks)
              .forEach(Runnable::run);
        return instance.predicate;
    }
    
    public static void like(Path&lt;String&gt; path, String value) {
        if (StringUtils.isNotEmpty(value)) {
            CriteriaBuilder c = instance.cb;
            instance.predicate = c.and(instance.predicate,
                                       c.like(c.lower(path), &quot;%&quot; + value.toLowerCase() + &quot;%&quot;));
        }
    }

    public static &lt;T&gt; void eq(Path&lt;T&gt; path, T value) {
        if (value != null) {
            CriteriaBuilder c = instance.cb;
            instance.predicate = c.and(instance.predicate, c.equal(path, value));
        }
    }

    public static &lt;T extends Enum&lt;T&gt;&gt; void eqEnumVal(Path&lt;T&gt; path, Class&lt;T&gt; enumType, String name) {
        if (StringUtils.isNotEmpty(name)) {
            instance.predicate = eq(instance.predicate, instance.cb, path, Enum.valueOf(enumType, name));
        }
    }

}       

Then you could invoke as below:
return PredicateBuilder.with(predicate, cb)
                       .tasks(() -&gt; eqEnumVal(root.get(regType), RegType.class, filter.getRegType()),
                              () -&gt; eq(userJoin.get(Acct_.id), filter.getAcctId()),
                              () -&gt; like(root.get(regNumber), filter.getRegNumber())
                       .build();

```