📘 Premium Read: Access my best content on Medium member-only articles — deep dives into Java, Spring Boot, Microservices, backend architecture, interview preparation, career advice, and industry-standard best practices.
✅ Some premium posts are free to read — no account needed. Follow me on Medium to stay updated and support my writing.
🎓 Top 10 Udemy Courses (Huge Discount): Explore My Udemy Courses — Learn through real-time, project-based development.
▶️ Subscribe to My YouTube Channel (172K+ subscribers): Java Guides on YouTube
Java’s Stream API introduced in Java 8 has revolutionized how we process data in collections. But many Java codebases still contain imperative-style loops, verbose if
conditions, and manual data transformations that can be simplified using Streams.
In this article, we’ll explore practical refactoring tricks using Java Streams — with before-and-after examples drawn from real-world legacy patterns.
🧱 Why Refactor Legacy Code to Use Streams?
- ✅ Improved readability and maintainability
- ✅ Eliminates boilerplate code
- ✅ Encourages functional, declarative programming
- ✅ Better support for parallel processing
🧪 Sample Domain Model
We’ll use this model throughout the article:
record Employee(String name, String department, double salary) {}
Sample list:
List<Employee> employees = List.of(
new Employee("Amit", "HR", 35000),
new Employee("Neha", "IT", 80000),
new Employee("Ravi", "HR", 45000),
new Employee("Ritu", "IT", 95000)
);
✅ 1. Replace for
Loops with forEach()
❌ Before:
for (Employee emp : employees) {
System.out.println(emp.name());
}
✅ After:
employees.forEach(emp -> System.out.println(emp.name()));
✅ 2. Replace for
+ if
with filter()
❌ Before:
List<Employee> result = new ArrayList<>();
for (Employee emp : employees) {
if ("HR".equals(emp.department())) {
result.add(emp);
}
}
✅ After:
List<Employee> result = employees.stream()
.filter(emp -> "HR".equals(emp.department()))
.toList();
✅ 3. Replace Loop with map()
❌ Before:
List<String> names = new ArrayList<>();
for (Employee emp : employees) {
names.add(emp.name());
}
✅ After:
List<String> names = employees.stream()
.map(Employee::name)
.toList();
✅ 4. Replace Aggregation with mapToX()
+ sum()
❌ Before:
double total = 0;
for (Employee emp : employees) {
total += emp.salary();
}
✅ After:
double total = employees.stream()
.mapToDouble(Employee::salary)
.sum();
✅ 5. Grouping Data with Collectors.groupingBy()
❌ Before:
Map<String, List<Employee>> grouped = new HashMap<>();
for (Employee emp : employees) {
grouped.computeIfAbsent(emp.department(), k -> new ArrayList<>()).add(emp);
}
✅ After:
Map<String, List<Employee>> grouped = employees.stream()
.collect(Collectors.groupingBy(Employee::department));
✅ 6. Count Items per Group
Map<String, Long> countByDept = employees.stream()
.collect(Collectors.groupingBy(
Employee::department,
Collectors.counting()
));
✅ 7. Find Maximum Salary per Department
Map<String, Optional<Employee>> topSalaries = employees.stream()
.collect(Collectors.groupingBy(
Employee::department,
Collectors.maxBy(Comparator.comparingDouble(Employee::salary))
));
✅ 8. FlatMap Nested Lists
Let’s say each department has a list of employees.
record Department(String name, List<Employee> team) {}
List<Department> departments = List.of(
new Department("HR", List.of(new Employee("Amit", "HR", 35000))),
new Department("IT", List.of(new Employee("Neha", "IT", 80000), new Employee("Ritu", "IT", 95000)))
);
✅ Flatten all employees:
List<Employee> allEmployees = departments.stream()
.flatMap(dept -> dept.team().stream())
.toList();
✅ 9. Sorting Streams
Sort by salary descending:
List<Employee> sorted = employees.stream()
.sorted(Comparator.comparingDouble(Employee::salary).reversed())
.toList();
✅ 10. Chaining Multiple Operations
Goal: Get names of all IT employees earning over ₹90,000
List<String> highEarners = employees.stream()
.filter(emp -> "IT".equals(emp.department()))
.filter(emp -> emp.salary() > 90000)
.map(Employee::name)
.toList();
⚠️ When Not to Use Streams
Situation | Recommendation |
---|---|
Simple for loops with side effects |
Prefer imperative loops |
Performance-critical sections | Benchmark before converting |
Code with shared mutable state | Avoid streams, prefer clarity |
Deep nesting/complex conditions | Split logic before streaming |
🧠 Tips for Refactoring
Tip | Why It Helps |
---|---|
Start with small loops | Low-risk, easy wins |
Use method references | Improves readability |
Use meaningful variable names | Avoid confusion in chained operations |
Refactor into helper methods | Keeps stream pipelines clean |
✅ Final Thoughts
Refactoring legacy code with Java Streams is a powerful way to modernize your codebase. It helps eliminate boilerplate, improve readability, and introduces functional programming concepts into your day-to-day Java work.
Start small, measure impact, and don’t refactor blindly — always prioritize clarity and maintainability.
Comments
Post a Comment
Leave Comment