This content originally appeared on DEV Community and was authored by bryam vega
It looked elegant, concise, and modern. But when we revisited the code six months later, nobody understood what it did. Was it mastery? Or just over-engineered cleverness?
Java Streams have become a hallmark of modern Java development. Introduced in Java 8, they offer a functional approach to working with collections, enabling developers to write concise and expressive code. But as with any tool, their effectiveness depends on how and when they are used.
What is a Stream?
The Streams were introduced in Java 8, and although Java is currently in versions higher than 21, this version marked an inflection point in the way Java is used nowadays. Within it is the API Stream that nowadays is too much used by Java programmers.
Streams provide a functional and declarative way of processing data. They allow to manipulate data collections efficiently, using operations such as map, filter, reduce, among others. For example:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// whitout streams
List<Integer> evenNumbers = new ArrayList<>();
for (Integer number : numbers) {
if (number % 2 == 0) {
evenNumbers.add(number);
}
}
// with streams
List<Integer> evenNumbers = numbers.stream()
.filter(number -> number % 2 == 0)
.collect(Collectors.toList());
This approach is modern because:
- It reduces boilerplate code.
- It promotes functional programming.
- It allows parallel operations easily with
parallelStream()
.
But in my experience, the excessive use of streams ends up complicating the understanding of the code, so simplicity does not always mean clarity, and that is where the problems arise.
The senior-junior dilemma: elegant but incomprehensible vs. simple but robust
When trying to explain the importance of Streams, most of the time I've come across images like this:
And I ask myself... 🤔 why is Senior level using Stream to solve this problem?
Let’s break it down.
- The "junior" approach: A straightforward loop, simple and clear. It works, it’s easy to read, and any developer—junior or senior—can understand it without much effort.
- The "senior" approach: Using Streams to achieve the same result but with more compact, declarative code. At first glance, it might seem more sophisticated or "modern."
But here’s the catch: Does using Streams in this scenario actually add value? Or does it introduce unnecessary complexity, just to showcase a tool?
Let's go with another much more crazy example that I saw and it made my head hurt 😵💫😵💫.
Imagine that you are watching programming videos and suddenly a guy appears and presents you with the following code.
public class CodeVerification {
private static final String NUMBERS = "12345";
private static final Integer LENGTH_CODE = 10;
private final Random random;
public CodeVerification(Random random) {
this.random = random;
}
public String generateCode(){
var builder = new StringBuilder();
for (int i = 0; i < LENGTH_CODE; i++) {
var index = random.nextInt(NUMBERS.length());
builder.append(NUMBERS.charAt(index));
}
return builder.toString();
}
}
Now, this developer asks you to refactor the generateCode()
method. He presents the following solution:
public String generateCode(){
return IntStream.range(0, LENGTH_CODE)
.mapToObj(i -> random.nextInt(NUMBERS.length()))
.map(NUMBERS::charAt)
.collect(StringBuilder::new, StringBuilder::append,
StringBuilder::append)
.toString();
}
Do you see it now? 🧐🧐 You do realize what I mean right!!!!
Both codes work exactly the same, but what happens?? We go from a perfectly understandable, readable and maintainable code to a code that is difficult to understand and that does not add value or improve the performance of the code. Also, the code is not refactored, that's a topic we can talk about in another post.
So, with that, I come to the conclusion that a Senior is not someone who knows how to use Streams perfectly and applies them everywhere with the justification of "refactoring" the code. Being a Senior is understanding that refactoring doesn’t always mean making the code more "modern" or "functional," but rather making it clearer, more efficient, and easier to maintain.
A Senior knows when to use a simple for
loop because it’s the most straightforward and effective solution, and when to choose Streams when they truly add clarity or performance benefits. The key is understanding the problem, not the tool. If the traditional solution is simpler and keeps the code readable, there’s no need to complicate it with Streams just to follow a trend.
The real value of a Senior lies in their ability to make pragmatic decisions, based on the project's context and the team's needs, rather than just showing off skills with a tool. A Senior understands that sometimes less is more.
With the above example, does this mean that we should not use Streams? 😢😢😢 Absolutely NOT, here I show you a way to use streams to solve the same problem, keeping code readability, however this does not mean that it is more optimal.
public String generateCode(){
IntStream randomIndexes = random.ints(LENGTH_CODE, 0, NUMBERS.length());
Stream<Character> characters = randomIndexes.mapToObj(NUMBERS::charAt);
String code = characters.map(String::valueOf)
.collect(Collectors.joining());
return code
}
This is more readable, right? This is simply because we separate the stream into steps, one of the best practices of Streams is to ❌ NOT CONCATENATE SEVERAL STEPS IN A SINGLE STEP.
Best practices: How to use Streams without abuse
Here are some tips that I have acquired throughout my experience in working with streams
- Prioritize Readability Over Conciseness:If a Stream is difficult to understand at first glance, it probably needs refactoring.
- Avoid Excessive Nesting of Operations: Break complex operations into intermediate steps with descriptive names
- Don’t Use Streams When a Simple Loop Is Enough: For simple tasks like modifying a single element or performing basic calculations, Streams may be unnecessary
-
Use Parallel Streams with Caution: While parallel Streams
(parallelStream())
can improve performance, incorrect use may lead to concurrency issues or performance degradation. - Add Comments Where Necessary: If you use a complex operation in a Stream, document its purpose so others can understand it quickly.
Conclusion
Streams are a powerful feature in modern Java, but their true value lies in knowing when and how to use them effectively. There is no "seniority" in using or not using Streams—it’s just a different way of programming. A Senior developer understands that it’s not about the tool, but about choosing the right solution for the problem. Whether that means using Streams, loops, or any other approach, the key is writing code that is clean, maintainable, and easy to understand.
The goal should always be clean, maintainable, and understandable code, remember: clarity always comes first.
Streams can be incredibly powerful when used correctly, but overusing them can lead to unnecessary complexity and remember: less is often more.
Happy coding!! ❤️
This content originally appeared on DEV Community and was authored by bryam vega
bryam vega | Sciencx (2025-01-22T22:24:54+00:00) Streams in Java: Mastering or abuse?. Retrieved from https://www.scien.cx/2025/01/22/streams-in-java-mastering-or-abuse-2/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.