Raw data doesn't do management any good. They need the numbers transformed into something readable and meaningful.

That's the kind of thing you can accomplish with the map() method on a Java Stream object.

In this guide, I'll show you how to do just that. With something resembling a real-world application.

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?

A Histogram If You Please

Management wants a histogram. Because that's the kind of stuff management always wants.

In this case it's about sales contacts who completed the lead form on the website. Management wants to know what time of day people are completing the form.

Fortunately, you track web form completions as sales activities. So all you need to do is grab the web form completion activities and send back the hour of the day when each form fill-out occurred.

You can send that data back to a client and the client can use the info to draw a pretty histogram on a website.

But alas, I won't be covering the UI stuff here. That's for another guide.

In this guide, I'll just cover how to use a Java Stream to send back the data that management needs.

A Tale of Two Maps

To fulfill this requirement, you'll need to make use of the map() method.  Not once but twice.

I'll explain more about that in a moment.

First, let me explain what map() does. 

It takes the elements in the Stream object and translates them into something else. That "something else" can be anything you want.

You can even translate objects into primitives.

However, keep in mind that map() is an intermediate operation. That means it returns another Stream object. You still need to translate that object into something like a List.

Unless, of course, you still have more Stream-related work to do. That's what you'll see here.

So take a look at this code and I'll go over it on the other side.

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

List<String> times = activities
                        .filter(activity -> activity.getType() != null && "Web Form Completion".equals(activity.getType().getName()))
                        .map(activity -> activity.getStartDate())
                        .map(dateAsNumber -> DATE_FORMAT.format(new Date(dateAsNumber)))


The first line goes out to the MongoDB collection and retrieves all activities in descending order of start date. That's the easy part.

But the requirement calls for looking only at activity types that involve a web form completion. So the code needs to filter it.

To do that, it first translates the List object into a Stream object with the stream() method. 

Then, the code invokes the filter() method on the Stream object. If you want to learn more about how that works, feel free to check out my guide on filtering with Java Streams.

In this case, the filter grabs all activities with the activity type name set to "Web Form Completion." That's nothing too complicated.

But filter(), like map(), is an intermediate operation. That means it returns a Stream object. 

That's great news here because the code can continue its work without the need to re-instantiate anything. It's a process called method-chaining and it works with fluent interfaces.

Now the code has the data it needs (web form completion activities) but not in the format it needs (hour of the day when the form completion occurred).

So now the code needs to grab the date and time when the contact completed the form.

That's what the first map() function does. It takes the whole Activity object and gets only the start date. 

That start date, by the way, is a Long object. It represents the number of milliseconds since January 1, 1970.

But... what the heck good is that kind of a number to management? Folks in the C-Suite don't want to see the date represented by a huge number in their reports. They want something they can understand.

So the code needs to do another map(). Fortunately, that's easy because, once more with feeling, the map() method is an intermediate operation that returns a Stream object.

The second map() takes the sequence of Long numbers created by the first map() method and translates them into a formatted String.

How does it do all that? First, it instantiates a new Date object. It does that by passing the Long value into the constructor.

When that happens, the code has a handle on a Date object that represents the date and time the sales contact completed the web form.

But management just wants to see the time of day, not the whole date.

So the code formats the Date object into a String representing the hour of the day when the contact completed the form. It does that with a constant named DATE_FORMAT that looks like this:

private static final DateFormat DATE_FORMAT = new SimpleDateFormat("hh:00");

All that does is grab the hour value from the Date object and format it with the hour of the day (in military time) followed by ":00". 

So a Date object with 6:31 AM as the time would format to "06:00".

Once that formatting is completed, the code translates the Stream object into a List of String objects with the aid of the collect() method.

Two Maps? Really?

Yes, you could absolutely combine those two maps into one like this:

List<String> times = activities
                        .filter(activity -> activity.getType() != null && "Web Form Completion".equals(activity.getType().getName()))
                        .map(activity -> DATE_FORMAT.format(new Date(activity.getStartDate())))

In fact, you'd probably want to do that in a Big Data situation.

But it's often a good idea not to bite off more than you can chew with a single map() method. Take it in stages so it's easier to debug.

Then you can always optimize the code later on.

A Lovely Test

If you take another look at the original code block, you'll see this line at the end:


That prints out the final List object in beautiful red letters.

So if you run that whole code block with the dataset I referenced above, you'll get output that looks like this:

[07:00, 07:00, 02:00]

That's exactly what you'd expect based on the data: two web form completions during the 7AM hour and one during the 2AM hour (somebody had insomnia).

Now you can give that data to whoever needs it. And it's readable.

However, if you want to see how to take this requirement to the next level, be sure to check out the guide on using Collectors.groupingBy().

Wrapping It Up

Over to you. Take what you've learned here with the map() method and use it to suit your own requirements.

Think about how you can retrieve the data stored in your database and translate it to meaningful info suitable for reports. 

You may have to create some custom objects to make that happen. That's okay, too.

But whatever you decide to do, just make sure you have fun.

Photo by Vlada Karpovich from Pexels