Skip to content
CMO & CTO
CMO & CTO

Closing the Bridge Between Marketing and Technology, By Luis Fernandez

  • Digital Experience
    • Experience Strategy
    • Experience-Driven Commerce
    • Multi-Channel Experience
    • Personalization & Targeting
    • SEO & Performance
    • User Journey & Behavior
  • Marketing Technologies
    • Analytics & Measurement
    • Content Management Systems
    • Customer Data Platforms
    • Digital Asset Management
    • Marketing Automation
    • MarTech Stack & Strategy
    • Technology Buying & ROI
  • Software Engineering
    • Software Engineering
    • Software Architecture
    • General Software
    • Development Practices
    • Productivity & Workflow
    • Code
    • Engineering Management
    • Business of Software
    • Code
    • Digital Transformation
    • Systems Thinking
    • Technical Implementation
  • About
CMO & CTO

Closing the Bridge Between Marketing and Technology, By Luis Fernandez

Streams Are Here in Java 8

Posted on April 11, 2014 By Luis Fernandez

Java 8 just landed, and the new Stream API is the feature I keep reaching for first.

Streams flip our usual loop habit on its head. Instead of telling the computer how to iterate and where to push items, we describe what we want, and Java takes care of the walk. Pair that with lambda expressions and method references, and you get code that reads closer to the intent in your head. If you ever stared at a chain of for loops and temporary lists and felt the friction, streams will feel like a breath of clean air.

Take a common case. Filter names, sort them, and collect the top few. First the old way. You can almost hear the ceremony creaking.

List<String> names = Arrays.asList("Ana", "bob", "CARL", "Bea", "alex");
List<String> result = new ArrayList<>();

for (String n : names) {
    if (n.length() >= 3) {
        result.add(n.toUpperCase());
    }
}

Collections.sort(result);
List<String> top2 = new ArrayList<>();
for (int i = 0; i < Math.min(2, result.size()); i++) {
    top2.add(result.get(i));
}

Now with streams and lambdas. Fewer moving parts, more signal.

List<String> top2 =
    names.stream()
         .filter(n -> n.length() >= 3)
         .map(String::toUpperCase)
         .sorted()
         .limit(2)
         .collect(Collectors.toList());

The punchline is not fewer lines. It is readability. Each step says what it does. Filter. Map. Sort. Limit. Collect. Your eye can scan the pipeline and hold the whole thing in working memory. That is the win. The bonus is that streams are lazy. Nothing happens until the terminal operation. So you can chain without paying for steps you never reach.

There is a second theme here. Internal iteration. In the classic loop, you pull from the collection and push into the next one. With streams, you let Java pull for you. This opens the door to parallel work with one word. You can say names.parallelStream and it will use a fork join pool under the covers. That said, do not rush to sprinkle parallel everywhere. Shared data with side effects can lead to surprises, and order matters when you care about the exact sequence.

A small example with numbers. Sum of squares for even numbers, the compact way.

int sum =
    IntStream.rangeClosed(1, 100)
             .filter(x -> x % 2 == 0)
             .map(x -> x * x)
             .sum();

Streams play nice with Collectors. You can group, partition, join strings, and more. Here is a quick grouping sample with a custom downstream collector.

Map<Integer, List<String>> byLength =
    names.stream()
         .collect(Collectors.groupingBy(String::length));

String joined =
    names.stream()
         .map(String::toLowerCase)
         .sorted()
         .collect(Collectors.joining(", "));

One more that many of us wrote by hand too many times. Count items by a key and keep the top N. So much boilerplate falls away.

Map<Character, Long> counts =
    names.stream()
         .map(s -> s.substring(0, 1).toLowerCase().charAt(0))
         .collect(Collectors.groupingBy(c -> c, Collectors.counting()));

List<Map.Entry<Character, Long>> top =
    counts.entrySet().stream()
          .sorted(Map.Entry.<Character, Long>comparingByValue().reversed())
          .limit(3)
          .collect(Collectors.toList());

About side effects. Streams are happiest when each step is pure. No mutation of shared state inside map or filter. If you must peek for debugging, there is a tool for that.

List<String> debugged =
    names.stream()
         .peek(s -> System.out.println("raw: " + s))
         .filter(s -> s.length() > 2)
         .peek(s -> System.out.println("filtered: " + s))
         .map(String::toUpperCase)
         .collect(Collectors.toList());

Order is preserved for streams from ordered sources. That means List streams keep the order you expect. If you switch to parallel, most operations still keep that order, but some terminal operations like findAny are allowed to return the first seen in no particular way. If you need stable order, there is forEachOrdered. As always, measure before trying to outsmart the runtime.

Streams also play well with Optional. You can express missing values without magic null checks sprinkled everywhere. For example, first name longer than three letters, uppercase, or a default.

String first =
    names.stream()
         .filter(s -> s.length() > 3)
         .map(String::toUpperCase)
         .findFirst()
         .orElse("NONE");

If you are thinking about migration, you do not have to rewrite everything. Bring streams into new code, then refactor old hotspots where loops get hairy. You can mix and match with classic code. Collections remain the bedrock. Streams are a new view on top of them, not a replacement for them. The learning curve is short if you start with small wins and keep an eye on clarity.

Stream API, lambda expressions, method references, and collectors are not just shiny new toys. They are a practical way to cut noise and say what you mean. Less ceremony. More intent. Your future self who reads the code will be thankful.

Streams are here.

Time to write code that reads like a thought.

Code Development Practices Software Engineering coding-practicesjavaspring

Post navigation

Previous post
Next post
  • Digital Experience (94)
    • Experience Strategy (19)
    • Experience-Driven Commerce (5)
    • Multi-Channel Experience (9)
    • Personalization & Targeting (21)
    • SEO & Performance (10)
  • Marketing Technologies (92)
    • Analytics & Measurement (14)
    • Content Management Systems (45)
    • Customer Data Platforms (4)
    • Digital Asset Management (8)
    • Marketing Automation (6)
    • MarTech Stack & Strategy (10)
    • Technology Buying & ROI (3)
  • Software Engineering (310)
    • Business of Software (20)
    • Code (30)
    • Development Practices (52)
    • Digital Transformation (21)
    • Engineering Management (25)
    • General Software (82)
    • Productivity & Workflow (30)
    • Software Architecture (85)
    • Technical Implementation (23)
  • 2025 (12)
  • 2024 (8)
  • 2023 (18)
  • 2022 (13)
  • 2021 (3)
  • 2020 (8)
  • 2019 (8)
  • 2018 (23)
  • 2017 (17)
  • 2016 (40)
  • 2015 (37)
  • 2014 (25)
  • 2013 (28)
  • 2012 (24)
  • 2011 (30)
  • 2010 (42)
  • 2009 (25)
  • 2008 (13)
  • 2007 (33)
  • 2006 (26)

Ab Testing Adobe Adobe Analytics Adobe Target AEM agile-methodologies Analytics architecture-patterns CDP CMS coding-practices content-marketing Content Supply Chain Conversion Optimization Core Web Vitals customer-education Customer Data Platform Customer Experience Customer Journey DAM Data Layer Data Unification documentation DXP Individualization java Martech metrics mobile-development Mobile First Multichannel Omnichannel Personalization product-strategy project-management Responsive Design Search Engine Optimization Segmentation seo spring Targeting Tracking user-experience User Journey web-development

©2025 CMO & CTO | WordPress Theme by SuperbThemes