Expand Authors Top

If you have a few years of experience in the Java ecosystem and you’d like to share that with the community, have a look at our Contribution Guidelines.

Expanded Audience – Frontegg – Security (partner)
announcement - icon User management is very complex, when implemented properly. No surprise here.

Not having to roll all of that out manually, but instead integrating a mature, fully-fledged solution - yeah, that makes a lot of sense.
That's basically what Frontegg is - User Management for your application. It's focused on making your app scalable, secure and enjoyable for your users.
From signup to authentication, it supports simple scenarios all the way to complex and custom application logic.

Have a look:

>> Elegant User Management, Tailor-made for B2B SaaS

November Discount Launch 2022 – Top
We’re finally running a Black Friday launch. All Courses are 30% off until end-of-day today:


NPI – Lightrun – Spring (partner)

We rely on other people’s code in our own work. Every day. It might be the language you’re writing in, the framework you’re building on, or some esoteric piece of software that does one thing so well you never found the need to implement it yourself.

The problem is, of course, when things fall apart in production - debugging the implementation of a 3rd party library you have no intimate knowledge of is, to say the least, tricky. It’s difficult to understand what talks to what and, specifically, which part of the underlying library is at fault.

Lightrun is a new kind of debugger.

It's one geared specifically towards real-life production environments. Using Lightrun, you can drill down into running applications, including 3rd party dependencies, with real-time logs, snapshots, and metrics. No hotfixes, redeployments, or restarts required.

Learn more in this quick, 5-minute Lightrun tutorial:

>> The Essential List of Spring Boot Annotations and Their Use Cases

1. Overview

In this quick article, we'll extend the advanced search operations that we implemented in the previous article and include OR-based search criteria into our REST API Query Language.

2. Implementation Approach

Before, all the criteria in the search query parameter formed predicates grouped only by AND operator. Let's change that.

We should be able to implement this feature either as a simple, quick change to existing approach or a new one from scratch.

With the simple approach, we'll flag the criteria to indicate that it must be combined using the OR operator.

For example, here is the URL to test the API for “firstName OR lastName”:


Note that we have flagged the criteria lastName with a single quote to differentiate it. We will capture this predicate for OR operator in our criteria value object – SpecSearchCriteria:

public SpecSearchCriteria(
  String orPredicate, String key, SearchOperation operation, Object value) {
      = orPredicate != null
      && orPredicate.equals(SearchOperation.OR_PREDICATE_FLAG);
    this.key = key;
    this.operation = operation;
    this.value = value;

3. UserSpecificationBuilder Improvement

Now, let's modify our specification builder, UserSpecificationBuilder, to consider the OR qualified criteria when constructing Specification<User>:

public Specification<User> build() {
    if (params.size() == 0) {
        return null;
    Specification<User> result = new UserSpecification(params.get(0));

    for (int i = 1; i < params.size(); i++) {
        result = params.get(i).isOrPredicate()
          ? Specification.where(result).or(new UserSpecification(params.get(i))) 
          : Specification.where(result).and(new UserSpecification(params.get(i)));
    return result;

4. UserController Improvement

Finally, let's set up a new REST endpoint in our controller to use this search functionality with OR operator. The improved parsing logic extracts the special flag that helps in identifying the criteria with OR operator:

public List<User> findAllByOrPredicate(@RequestParam String search) {
    Specification<User> spec = resolveSpecification(search);
    return dao.findAll(spec);

protected Specification<User> resolveSpecification(String searchParameters) {
    UserSpecificationsBuilder builder = new UserSpecificationsBuilder();
    String operationSetExper = Joiner.on("|")
    Pattern pattern = Pattern.compile(
      + operationSetExper 
      + ")(\\p{Punct}?)(\\w+?)(\\p{Punct}?),");
    Matcher matcher = pattern.matcher(searchParameters + ",");
    while (matcher.find()) {
        builder.with(matcher.group(1), matcher.group(2), matcher.group(3), 
        matcher.group(5), matcher.group(4), matcher.group(6));
    return builder.build();

5. Live Test With OR Condition

In this live test example, with the new API endpoint, we'll search for users by the first name “john” OR last name “doe”. Note that parameter lastName has a single quote, which qualifies it as an “OR predicate”:

private String EURL_PREFIX
  = "http://localhost:8082/spring-rest-full/auth/users/espec?search=";

public void givenFirstOrLastName_whenGettingListOfUsers_thenCorrect() {
    Response response = givenAuth().get(EURL_PREFIX + "firstName:john,'lastName:doe");
    String result = response.body().asString();


6. Persistence Test With OR Condition

Now, let's perform the same test we did above, at the persistence level for users with first name “john” OR last name “doe”:

public void givenFirstOrLastName_whenGettingListOfUsers_thenCorrect() {
    UserSpecificationsBuilder builder = new UserSpecificationsBuilder();

    SpecSearchCriteria spec 
      = new SpecSearchCriteria("firstName", SearchOperation.EQUALITY, "john");
    SpecSearchCriteria spec1 
      = new SpecSearchCriteria("'","lastName", SearchOperation.EQUALITY, "doe");

    List<User> results = repository

    assertThat(results, hasSize(2));
    assertThat(userJohn, isIn(results));
    assertThat(userTom, isIn(results));

7. Alternative Approach

In the alternate approach, we could provide the search query more like a complete WHERE clause of SQL query.

For example, here is the URL for a more complex search by firstName and age:

http://localhost:8080/users?search=( firstName:john OR firstName:tom ) AND age>22

Note that we have separated individual criteria, operators & grouping parenthesis with a space to form a valid infix expression.

Let us parse the infix expression with a CriteriaParser. Our CriteriaParser splits the given infix expression into tokens (criteria, parenthesis, AND & OR operators) and creates a postfix expression for the same:

public Deque<?> parse(String searchParam) {

    Deque<Object> output = new LinkedList<>();
    Deque<String> stack = new LinkedList<>();

    Arrays.stream(searchParam.split("\\s+")).forEach(token -> {
        if (ops.containsKey(token)) {
            while (!stack.isEmpty() && isHigerPrecedenceOperator(token, stack.peek())) {
                  ? SearchOperation.OR_OPERATOR : SearchOperation.AND_OPERATOR);
              ? SearchOperation.OR_OPERATOR : SearchOperation.AND_OPERATOR);

        } else if (token.equals(SearchOperation.LEFT_PARANTHESIS)) {
        } else if (token.equals(SearchOperation.RIGHT_PARANTHESIS)) {
            while (!stack.peek().equals(SearchOperation.LEFT_PARANTHESIS)) { 
        } else {
            Matcher matcher = SpecCriteraRegex.matcher(token);
            while (matcher.find()) {
                output.push(new SpecSearchCriteria(

    while (!stack.isEmpty()) {
    return output;

Let us add a new method in our specification builder, GenericSpecificationBuilder, to construct the search Specification from the postfix expression:

    public Specification<U> build(Deque<?> postFixedExprStack, 
        Function<SpecSearchCriteria, Specification<U>> converter) {

        Deque<Specification<U>> specStack = new LinkedList<>();

        while (!postFixedExprStack.isEmpty()) {
            Object mayBeOperand = postFixedExprStack.pollLast();

            if (!(mayBeOperand instanceof String)) {
                specStack.push(converter.apply((SpecSearchCriteria) mayBeOperand));
            } else {
                Specification<U> operand1 = specStack.pop();
                Specification<U> operand2 = specStack.pop();
                if (mayBeOperand.equals(SearchOperation.AND_OPERATOR)) {
                else if (mayBeOperand.equals(SearchOperation.OR_OPERATOR)) {
        return specStack.pop();

Finally, let us add another REST endpoint in our UserController to parse the complex expression with the new CriteriaParser:

public List<User> findAllByAdvPredicate(@RequestParam String search) {
    Specification<User> spec = resolveSpecificationFromInfixExpr(search);
    return dao.findAll(spec);

protected Specification<User> resolveSpecificationFromInfixExpr(String searchParameters) {
    CriteriaParser parser = new CriteriaParser();
    GenericSpecificationsBuilder<User> specBuilder = new GenericSpecificationsBuilder<>();
    return specBuilder.build(parser.parse(searchParameters), UserSpecification::new);

8. Conclusion

In this tutorial, we've improved our REST query language with the capability to search with an OR operator.

The full implementation of this article can be found in the GitHub project. This is a Maven-based project, so it should be easy to import and run as it is.

Next »
REST Query Language with RSQL
« Previous
REST Query Language – Advanced Search Operations
November Discount Launch 2022 – Bottom
We’re finally running a Black Friday launch. All Courses are 30% off until end-of-day today:


REST footer banner
Comments are closed on this article!