Filtering a list of customers in Java based on three criteria Let's assume that you need to filter customers based on 3 criteria:


name
tags assigned to customer
customer's location


For some reasons you have to do it directly in your program code, without database queries or PL/SQL for that matter.
Which of these two solutions do you find better in any terms? (e.g code readability, performance, cleanliness)

First example

public List&lt;Customer&gt; filterStepbyStep(List&lt;Customer&gt; customers, 
                                       String searchString, 
                                       List&lt;Tag&gt; selectedTags,
                                       List&lt;LocationId&gt; selectedLocations) {
    List&lt;Customer&gt; filteredCustomers = new ArrayList&lt;Customer&gt;(customers);
    boolean searchStringNotEmpty = searchString != null &amp;&amp; !searchString.trim().isEmpty();
    boolean locationsNotEmpty = selectedLocations != null &amp;&amp; selectedLocations.size() &gt; 0;
    boolean selectedTagsNotEmpty = selectedTags != null &amp;&amp; selectedTags.size() &gt; 0;

    filteredCustomers.removeIf(customer -&gt; {
        boolean valid = true;

        //searchString
        if (valid &amp;&amp; searchStringNotEmpty) {
            valid = customer.name.contains(searchString);
        }

        //locations
        if (valid &amp;&amp; locationsNotEmpty) {
            valid = selectedLocations.stream().anyMatch(locationId -&gt; locationId.equals(customer.locationId));
        }

        //tags
        if (valid &amp;&amp; selectedTagsNotEmpty) {
            boolean tagValid = false;
            for (Tag selectedTag : selectedTags) {
                for (Tag customerTag : customer.getTags()) {
                    if (selectedTag == customerTag) {
                        tagValid = true;
                        break;
                    }
                }
                if (tagValid) {
                    break;
                }
            }
            valid = tagValid;
        }

        return !valid;
    });

    return filteredCustomers;
}


Second example

public List&lt;Customer&gt; filterWithLambda(List&lt;Customer&gt; customers,
                                       String searchString, 
                                       List&lt;Tag&gt; selectedTags,
                                       List&lt;LocationId&gt; selectedLocations) {
    List&lt;Customer&gt; filteredCustomers = new ArrayList&lt;Customer&gt;(customers);
    boolean searchStringNotEmpty = searchString != null &amp;&amp; !searchString.trim().isEmpty();
    boolean locationsNotEmpty = selectedLocations != null &amp;&amp; selectedLocations.size() &gt; 0;
    boolean selectedTagsNotEmpty = selectedTags != null &amp;&amp; selectedTags.size() &gt; 0;

    filteredCustomers.removeIf(customer -&gt; {
        return (searchStringNotEmpty &amp;&amp; !customer.name.contains(searchString))
                || (locationsNotEmpty &amp;&amp; !selectedLocations.stream().anyMatch(locationId -&gt; locationId.equals(customer.locationId)))
                || (selectedTagsNotEmpty &amp;&amp; !selectedTags.stream().anyMatch(selectedTag -&gt;
                      customer.getTags().stream().anyMatch(customerTag -&gt;
                        selectedTag == customerTag)));
    });

    return filteredCustomers;
}



For me second example is more readable, I can immediately see that customer gets filtered out (removed) if any of 3 operands of OR operator is true.
On the other hand repeating if(valid) for each property in first example does not seem too clean to me. I also find it tiresome and tedious to follow valid variable. Having this kind of state with valid variable is IMO error-prone. 
The only reason I can see, that one would prefer first example is not being familiar with lambda expressions.

I've created small repl.it to try these approaches out.

If you can think of better way of doing this, than two above examples, then please feel free to post it here.
As far as which is easier to read, there isn't a clearly correct answer. The second is more shorter, but more cramped. I personally prefer code with a little more whitespace, such as the first example. Others prefer code that's more succinct. Which is more readable is probably a question to ask your coworkers.

Marking variables as final will clue the reader in that they don't change once assigned.

In camelCase, the method name should be filterStepByStep.

Both methods use lambdas, so the name filterWithLambda is misleading.

In recent versions of Java, you don't need to specify the type on the right hand side of a generic assignment. You can just use &lt;&gt;.

You can get rid of 'valid' by returning false early.

The equals check for locations can be cleaned up using customer.locationId::equals, or, better, by just using contains.

The check on tags can be cleaned up using Set operations.

I would consider renaming the variables to matchCustomerName, matchLocation, and matchTags to make the checks read more cleanly.

If you were to make all these changes, your code might look more like:

public static List&lt;Customer&gt; filterStepByStep(
        final List&lt;Customer&gt; customers,
        final String searchString,
        final List&lt;Tag&gt; selectedTags,
        final List&lt;LocationId&gt; selectedLocations) {

    final List&lt;Customer&gt; filteredCustomers = new ArrayList&lt;&gt;(customers);
    final boolean matchCustomerName = searchString != null &amp;&amp; !searchString.trim().isEmpty();
    final boolean matchLocation = selectedLocations != null &amp;&amp; selectedLocations.size() &gt; 0;
    final boolean matchTags = selectedTags != null &amp;&amp; selectedTags.size() &gt; 0;


    filteredCustomers.removeIf(customer -&gt; {
        if (matchCustomerName &amp;&amp; !customer.name.contains(searchString)) {
            return true;
        }

        if (matchLocation &amp;&amp; !selectedLocations.contains(customer.locationId)) {
            return true;
        }

        if (matchTags) {
            final Set&lt;Tag&gt; matchedTags = new HashSet&lt;&gt;(selectedTags);
            matchedTags.retainAll(customer.getTags());
            return matchedTags.isEmpty();
        }

        return false;
    });

    return filteredCustomers;
}

public List&lt;Customer&gt; filterWithLambda(
        final List&lt;Customer&gt; customers,
        final String searchString,
        final List&lt;Tag&gt; selectedTags,
        final List&lt;LocationId&gt; selectedLocations) {

    final List&lt;Customer&gt; filteredCustomers = new ArrayList&lt;&gt;(customers);
    final boolean matchCustomerName = searchString != null &amp;&amp; !searchString.trim().isEmpty();
    final boolean matchLocations = selectedLocations != null &amp;&amp; selectedLocations.size() &gt; 0;
    final boolean matchTags = selectedTags != null &amp;&amp; selectedTags.size() &gt; 0;

    filteredCustomers.removeIf(customer -&gt; {
        return (matchCustomerName &amp;&amp; !customer.name.contains(searchString))
                || (matchLocations &amp;&amp; !selectedLocations.contains(customer.locationId))
                || (matchTags &amp;&amp; !selectedTags.stream().anyMatch(customer.getTags()::contains));
    });

    return filteredCustomers;
}