Sometimes you end up with a List object that you need to whittle down to fewer elements. One way you can do that is with a Java Stream and the filter() method.

And in this guide, I'll show you how to make that dream become a reality.

Keep in mind, though, that filter() is an intermediate operation on a Stream object. That means when you invoke filter(), you aren't done.

Don't worry, though. I'll show you how to end with a clean finish once you've filtered out the elements you don't want.

Ready to go? Let's get started.

The CRM App

Let's say you're building a CRM app. It does the normal stuff that CRM apps do: tracks activities between sales reps and contacts.

Those activities get stored in a MongoDB database as documents. Each document persists info like the title of the activity as well as the type, outcome, location, start time, end time, notes, and the contact involved.

As it stands right now, if you retrieve all the documents from the activities collection, the resulting data set looks like the JSON dump at this link.

As you can see, we're not messing around here. You're going to be working with real-world data.

On the Java side, the Activity class with its related classes mimic the data set that you see above. You can see examples of those classes over on GitHub.

So you can just do a findAll() on that collection above and get a List of Java objects that represent that JSON output. Then, you can use a Java Stream to filter, find, and map as you see fit.

That's what you'll do in this guide.

And, yes, you could do that kind of stuff with MongoDB aggregations. But you're not here to learn about aggregations are you?

Streaming Service

Managers who use your CRM app want to know if the company is getting any leads from the web form on the company website. They'd like a feature that gives them a report about people who've completed the form.

To that end, your application will grab all the existing activities and filter them based on activity type. In this case, you'll want to only include acitivities with activity types named "Web Form Completion."

Now you could do the forEach() thing right off the list and filter it that way. But that would take a few more lines of code than you really need to write.

So use a Stream instead.

All you need to do to convert the List object to a Stream object is just invoke the stream() method. That's it.

Then, invoke the filter.

Here's what the whole thing looks like:

List<Activity> activities = activityRepo.findAll();

List<Activity> webFormActivities = activities
                .stream()
                .filter(activity -> activity.getType() != null && "Web Form Completion".equals(activity.getType().getName()))
                .collect(Collectors.toList());

try {
    ObjectMapper objectMapper = new ObjectMapper();
    System.err.println(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(webFormActivities));
} catch (Exception e) {
    e.printStackTrace();
}

Way up there at the first line, the code grabs all activities from the repository. That returns a List object.

Alas, though, that object contains too many elements. You only want activities with the type name of "Web Form Completion."

So that's when you pare it down with the assistance of Stream.  The next few lines of code take care of that.

Once the List object gets converted to a Stream, the code invokes the filter() method.

Now what's happening inside that filter() method? That looks kind of weird.

That filter method takes a single Predicate as its only parameter. If you want to learn more about that subject, you can check out my explainer on Predicates.

For now, though, be clear about this: what's happening inside that filter() method returns a boolean for each and every Activity object in the original List.

If the boolean is true, then that associated Activity gets included in the filtered list. Otherwise, it's out.

And how does the filter determine if the boolean is true or false? With the two tests you see inside the filter() method:

  • A check to make sure the ActivityType object isn't null
  • A check to make sure the name of the ActivityType object equals "Web Form Completion"

Every Activity object that passes that test gets included in the whittled down sequence.

But wait a second, wait a second, wait a second...

The filter() method doesn't return a List. What gives?

Intermediate Operations

That filter() method is what we call in Java parlance an "intermediate operation." What that means in non-geek speak is: you still have stuff to do.

In other words, once you invoke filter(), you aren't done.

You see, that filter() method returns another Stream object. In this case, it will include a sequence of elements that match the criteria you specify. 

But it's still a Stream. You don't want to be throwing Streams around. It's best to use a class that implements the Collection interface or one of its children.

So why does filter() return another Stream object instead of just giving you a List? Because you might have more work you want to do with the elements. 

The beautiful thing about Streams is that you can chain operations together to handle some really complex requirements.

But you don't need anything really sophisticated here. That's why you see that collect() method in the code above that follows filter(). It translates the Stream object to a List object.

In other words, it does the exact opposite of what the stream() method does.

That collect() method, by the way, is overloaded. It can accept a Supplier or a Collector.

Here, it accepts a Collector that returns the List object.

Testing It Out

Take another look at that code above and you'll see a try/catch block at the end. Inside that try/catch block the code prints out the Activity objects that got returned by the filter process in JSON format.

So let's see what happens if we run that code against the dataset I shared with you earlier.

Here's what happens:

[ {
  "id" : "6016b316f9722c71f96cddd6",
  "type" : {
    "id" : "6016a960ca8c08019b4dfcc0",
    "name" : "Web Form Completion",
    "icon" : "list_alt"
  },
  "title" : "Completed More Info web form on careydevelopment.us",
  "outcome" : null,
  "notes" : null,
  "location" : null,
  "startDate" : 1610305380000,
  "endDate" : 1610308980000,
  "contact" : {
    "id" : "6014199147692f2a4194ff9d",
    "firstName" : "Governor",
    "lastName" : "Rover",
    "account" : {
      "id" : "60141482b56f731fe77e902d",
      "name" : "No Moon"
    },
    "salesOwner" : {
      "id" : "6014081e221e1b534a8aa432",
      "firstName" : "Milton",
      "lastName" : "Jones",
      "username" : "milton"
    }
  }
}, {
  "id" : "601fd63fda249c7531fd28f1",
  "type" : {
    "id" : "6016a960ca8c08019b4dfcc0",
    "name" : "Web Form Completion",
    "icon" : "list_alt"
  },
  "title" : "Completed more info form on careydevelopment.us",
  "outcome" : null,
  "notes" : null,
  "location" : null,
  "startDate" : 1610452800000,
  "endDate" : null,
  "contact" : {
    "id" : "6014199147692f2a4194ff95",
    "firstName" : "Lucy",
    "lastName" : "Cheng",
    "account" : {
      "id" : "60141483b56f731fe77e902e",
      "name" : "Queen Inc."
    },
    "salesOwner" : {
      "id" : "6014081e221e1b534a8aa432",
      "firstName" : "Milton",
      "lastName" : "Jones",
      "username" : "milton"
    }
  }
}, {
  "id" : "601fe0c68fc6df7eddcf3ae8",
  "type" : {
    "id" : "6016a960ca8c08019b4dfcc0",
    "name" : "Web Form Completion",
    "icon" : "list_alt"
  },
  "title" : "Completed More Info web form on careydevelopment.us",
  "outcome" : null,
  "notes" : null,
  "location" : null,
  "startDate" : 1610369100000,
  "endDate" : null,
  "contact" : {
    "id" : "6014199147692f2a4194ff9b",
    "firstName" : "Frum",
    "lastName" : "Lezilia",
    "account" : {
      "id" : "60141483b56f731fe77e9034",
      "name" : "International Business"
    },
    "salesOwner" : {
      "id" : "6014081e221e1b534a8aa432",
      "firstName" : "Milton",
      "lastName" : "Jones",
      "username" : "milton"
    }
  }
} ]

It prints out three (3) objects. Each of which includes an activity type named "Web Form Completion."

In other words, the thing works.

Wrapping It Up

Remember folks, you can filter on any of the properties in the objects included in a List. You just need to implement the Predicate correctly.

And that's what you need to do next. Apply what you've learned here to your own software development efforts.

Also: think about ways you can use filter() with other Stream methods. You'll find that you can handle some fairly complex operations.

And, as always, have fun!

Photo by Dziana Hasanbekava from Pexels