It's often the case that the first truly shall be last.

In Java development, you might want to grab the last item from an array or List object.

Normally, that's fairly easy to do. Just grab the length of the array or List, subtract one, and grab that nth element.

But what if you're in the middle of a series of Stream operations? That makes things a little more interesting.

The folks at Oracle were kind enough to give you a findFirst() method so you can always grab the first element in a sequence. But what about the last?

They didn't give you that.

Fortunately, it ain't that difficult to work around the missing piece here. In this guide, I'll show you how to do it.

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.

Orders From on High

Management wants to see the last type of activity that any contact was involved with. 

The requirement is simple: given a contact ID, you need to return the most recent sales activity type for that contact. 

Fortunately, you'll already have activities sorted by date in ascending order.

Unfortunately, you won't have them sorted by date in descending order. Otherwise, you could just use findFirst().

So you're going to have to make up your own findLast() thing.

Stream On

To tackle this requirement, you'll use a Stream. That will make it easy for you to filter the data and grab the most recent activity type.

Here's what that code looks like in a testing environment:

String contactId = "6014199147692f2a4194ff9d";

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

Optional<Activity> lastActivityOpt = activities
                                       .stream()
                                       .filter(activity -> contactId.equals(activity.getContact().getId()))
                                       .reduce((previousActivity, currentActivity) -> currentActivity);

try {
    if (lastActivityOpt.isPresent()) {
        ObjectMapper objectMapper = new ObjectMapper();
        System.err.println(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(lastActivityOpt.get().getType()));
    }
} catch (Exception e) {
    e.printStackTrace();
}

The first line of code sets the ID of the contact you're searching for. In this case, it's Lucy Cheng's ID.

The next line gets all activities from the MongoDB collection. It simulates a situation in which you'd have access to a set of activity data that's sorted by date in ascending order.

The next few lines of code handle the streaming.

First, the code converts the List to a Stream using the stream() method.

Next, the filter() method narrows the list down to all activities that only involve Lucy Cheng. If you need to know more about how that works, feel free to check out my guide on filtering in Streams.

Once the filter() is completed, the Stream only includes Activity objects with Lucy Cheng as the contact. They're sorted in ascending order by date.

And that's where the reduce() method comes in handy. In this case, it's taking a single parameter: the BinaryOperator.

What does that do? It's an accumulator.

The accumulator in a reduce function iterates over all elements in the sequence, each time taking in two parameters. The first parameter is the result of its previous accumulation. The second parameter is the current element in the sequence.

It uses those two objects to return a result.

In this case, the operation is simple: it always returns the current element in the sequence.

Take a close look at this and you'll see that's what it does:

(previousActivity, currentActivity) -> currentActivity

That just says: "Given what I got last time and what I'm looking at this time, just give me what I'm looking at this time."

In other words, previousActivity is basically ignored. Poor guy.

And since reduce() is a terminal operation, the code is done with Stream operations at that point.

So the end result of that reduce() method is an Optional<Activity> object.

Why Optional? Because you could put in an ID for a contact that currently has no sales activities. Or a bogus ID.

In that case, you might have nothing.

Test Feeding

Now it's time to run that code above and make sure it works.

Pay attention to the try/catch block at the end and you'll see that it spits out the returned result in pretty red lettering.

So if you run it with the dataset that I referenced earlier, you should end up with something like this:

{
  "id" : "6016a960ca8c08019b4dfcbc",
  "name" : "Phone Call",
  "icon" : "phone"
}

That's the most recent activity type for Lucy Cheng.

In other words, it works.

A Caveat

Although this works for smaller datasets, you might find issues with larger datasets.

That's because of this ominous warning about reduce() in the JavaDocs: "but is not constrained to execute sequentially."

In other words, reduce() might not process the elements in the right order. In that case, you'd end up with the wrong element as the last result.

The safest route might be to just collect the filtered elements as a List using Collectors.toList() and grab the last element the old-fashioned way (the size of the List minus 1 is the last element).

But I have yet to see reduce() fail to respect order.

Wrapping It Up

That wasn't so bad, was it?

Now it's over to you. Use that reduce() method in your own code to grab that last element in a Stream sequence.

And be sure to tell Oracle that we need a findLast().

Have fun!

Photo by Konevi from Pexels