Skip to Content
Course content

266: Practice Exercise: Building a Word Frequency Counter with Streams

Click on the "Edit" button in the top corner of the screen to edit your slide content.

We've spent a lot of time talking about the theory of Streams, but the best way to actually "get" the functional style in Java is to build something that feels like a real-world utility. Today, we're building a word frequency counter. It's a classic problem, but it's perfect for demonstrating how we can chain transformations to turn a messy string of text into a structured map of counts.

Turning a string into a stream of words

I usually start by thinking about the data pipeline. We have a big block of text, and we want a Map<String, Long>. The first hurdle is getting that text into a Stream. I'll use split("\\s+") to break the string apart by any whitespace, and then wrap that array in Arrays.stream().

String text = "Java is great, and Java streams are powerful. Streams make Java better.";
long count = Arrays.stream(text.split("\\s+"))
                   .filter(word -> !word.isEmpty())
                   .count();

That's a start, but it's too simple. If we just count them now, we're going to run into a problem that I actually stumbled into the first time I wrote this logic in a production environment.

Dealing with the "Punctuation Trap"

Here is where I messed up during my first attempt: I forgot that split("\\s+") only handles spaces. If the text says "Java is great, and Java streams...", my stream will see "great," (with a comma) and "great" (without one) as two entirely different words. That's a bug.

To fix this, I need to normalize the data. I'll add a map step to convert everything to lowercase and use a regular expression to strip out any non-alphanumeric characters. It's much cleaner to do this early in the pipeline so the grouping logic doesn't have to worry about it.

Stream<String> wordStream = Arrays.stream(text.split("\\s+"))
    .map(String::toLowerCase)
    .map(word -> word.replaceAll("[^a-zA-Z0-9]", ""));

Now, "Great," becomes "great", and we're finally comparing apples to apples.

The magic of groupingBy and counting

Now for the heavy lifting. We need to group identical words together and count how many times each appears. In the old days, we would have created a HashMap, looped through the list, and checked if (map.containsKey(word)). That's too much boilerplate. Instead, we use Collectors.groupingBy.

The groupingBy collector takes two arguments here: the classifier (what we are grouping by) and a downstream collector (what to do with the values in that group). Since we want the total count, Collectors.counting() is our best friend.

Map<String, Long> frequencies = wordStream
    .filter(word -> !word.isEmpty()) // Remove strings that became empty after regex
    .collect(Collectors.groupingBy(
        word -> word, 
        Collectors.counting()
    ));

System.out.println(frequencies);

I prefer this approach because it's declarative. I'm not telling Java how to manage the map; I'm telling it what the result should be: "Group by the word itself, and count the occurrences." It's concise, and once you're used to it, it's much harder to introduce off-by-one errors or null pointer exceptions than it is with a manual loop.




📋 Practical Task

Exercise: Building a Log Level Analyzer

Imagine you have a raw log file represented as a single String. Each line starts with a log level (INFO, WARN, or ERROR) followed by a message. Your task is to create a program that counts the occurrences of each log level using Java Streams.

Requirements:

  • Start with this input string: "INFO: System started\nWARN: Low disk space\nERROR: Database connection failed\nINFO: User logged in\nERROR: NullPointerException in AuthService\nINFO: Heartbeat sent"
  • Split the string into lines.
  • Extract only the log level (the part before the colon).
  • Use Collectors.groupingBy and Collectors.counting() to produce a Map<String, Long>.
  • Print the final map to the console.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.