Back to Java Course

Java Course

screenshot of the app

screenshot of the app

Architect-level answer strategy (for interviews)

"I use streams for readable data transformations such as filtering, mapping, aggregation, and sorting. For very complex logic, side effects, checked-exception-heavy code, or performance-critical hot paths, I prefer a simple loop for clarity and control."

Stream API is used to process of collection of objects.

A stream is not a datastructure instead it takes input from Collections,Arrays or I/O channels.

Stream will not change the original datastructure rather they produce result based on the pipeline methods.

What is the differences between Java.util.streams and Java.io streams?

java.util streams meant for processing objects from the collection. Ie, it represents a stream of objects from the collection but Java.io streams meant for processing binary and character data with respect to file. i.e it represents stream of binary data or character data from the file .hence Java.io streams and Java.util streams both are different.

What is the difference between collection and stream?

  • If we want to represent a group of individual objects as a single entity then We should go for collection.
  • If we want to process a group of objects from the collection then we should go for streams.
  • We can create a stream object to the collection by using stream() method of Collection interface. stream() method is a default method added to the Collection in 1.8 version.

default Stream<T> stream()

List nums = Arrays.asList(1,2,3,4); Stream s = nums.stream();

Stream is an interface present in java.util.stream. Once we got the stream, by using that we can process objects of that collection.

Why we need stream?

  1. We can achieve Functional Programming
  2. Code Reduce
  3. Bulk operation
  4. Parallel streams make it very easy for multithreaded operations

Stream Pipeline

A Stream pipeline consist of source, a zero or more intermediate operations and a terminal operation.

Source -> Filter -> Sort -> Map -> Collect

  1. Stream Source can be created from
    • Collections
    • Lists
    • Sets
    • ints
    • longs
    • doubles
    • arrays
    • lines of a file.
  2. Stream operations will be either terminal or intermediate.

Intermediate operations

Intermediate operations such as Filter,Map or sort return a stream so we can multiple intermediate operations.

Order matters for large datasets: filter first and then sort or map.

For very large datasets use ParallelStream to enable multiple threads.

Intermediate operations include

  • distinct()
  • filter()
  • flatMap()
  • map()
  • peek() – used for debugging / side-effects mid-pipeline, does not consume the stream
  • skip()
  • sorted()
  • limit()

Note: anyMatch(), allMatch(), noneMatch(), and findFirst()/findAny() are terminal operations, not intermediate — they short-circuit and return a boolean/Optional, so no further stream operation can be chained after them.

Filtering

We can configure a filter to filter elements from the collection based on some boolean condition by using filter()method of Stream interface.

public Stream<T> filter(Predicate<? super T> predicate)

here (Predicate<T > t ) can be a boolean valued function/lambda expression

For example,

Stream s = c.stream(); Stream s1 = s.filter(i -> i%2==0);

Hence to filter elements of collection based on some Boolean condition we should go for filter() method.

Mapping

If we want to create a separate new object, for every object present in the collection based on our requirement then we should go for map() method of Stream interface.

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

For example,

Stream s = c.stream(); Stream s1 = s.map(i-> i+10);

Terminal operations

Terminal operations such as forEach,collect or reduce are either void or return a non stream result.

Only one terminal operation is allowed.

forEach applies the same function to each element.

collect saves elements into a collection

other options reduce the stream to a single summary element

  • collect()
  • count()
  • max()
  • min()
  • reduce()
  • summaryStatistics()
  • forEach()
  • anyMatch() / allMatch() / noneMatch()
  • findFirst() / findAny()
  • toArray()

Processing by collect() method

This method collects the elements from the stream and adding to the specified to the collection indicated (specified) by argument.

For example, To collect only even numbers from the array list

l1.stream().filter(i -> i%2==0).collect(Collectors.toList());

Let's see another example, Program for map() and collect() Method

l.stream().map(s ->s.toUpperCase()).collect(Collectors.toList());

Processing by count() method

This method returns number of elements present in the stream.

public long count()

For example,

l.stream().filter(s ->s.length()==5).count();

Processing by sorted() method

If we sort the elements present inside stream then we should go for sorted() method.

the sorting can either default natural sorting order or customized sorting order specified by comparator.

sorted()- default natural sorting order

sorted(Comparator c)-customized sorting order.

For example,

List<String> l3=l.stream().sorted().collect(Collectors.toList()); System.out.println("according to default natural sorting order:"+l3); List<String> l4=l.stream().sorted((s1,s2) -> -s1.compareTo(s2)).collect(Collectors.toList()); System.out.println("according to customized sorting order:"+l4);

Processing by min() and max() methods

min(Comparator c) - returns minimum value according to specified comparator.

max(Comparator c) - returns maximum value according to specified comparator.

For example,

String min=l.stream().min((s1,s2) -> s1.compareTo(s2)).get(); System.out.println("minimum value is:"+min); String max=l.stream().max((s1,s2) -> s1.compareTo(s2)).get(); System.out.println("maximum value is:"+max);

Processing by reduce() method

reduce will take a BinaryOperator<T> as an argument.

For example, we need find sum of numbers in a List

List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5, 6, 7); System.out.println(nums.stream().reduce((x, y) -> x + y).get());

Map vs FlatMap

Map

Transforms the elements into something else. It accepts a function to apply to each element and returns a new Stream of values returned by the passed function.

It takes function object as the parameter e.g.: Function, ToIntFunction, ToIntDoubleFunction, ToLongFunction.

FlatMap

Combination of Map and Flat operations.So, we first apply map operation on each element and then flattens the result.

So, if function used by Map is returning a single value, then map is ok. But, if function used by Map operation is returning a stream of list or stream of stream, then we need to use flatmap to get stream of values.

List evens = Arrays.asList(2,4,6); List odds = Arrays.asList(3,5,7); List primes = Arrays.asList(2,3,5,7,11); List numbers = Stream.of(evens, odds, primes).flatMap(list -> list.stream()).collect(Collectors.toList()); System.out.println(numbers); // [2,4,6,3,5,7,2,3,5,7,11]

Common Collectors reference

Collector | Purpose ------------------------------------|---------------------------------------------------------------------------------------------------------------------- toList() | Immutable List (Java 16+); use Collectors.toList() for a mutable one pre-16 toSet() | Collects into a Set toMap(k, v) | Builds a Map; provide a merge function toMap(k, v, (a,b) -> a) if duplicate keys are possible, or it throws IllegalStateException joining(delim) | Joins Strings with a delimiter groupingBy(fn) | Groups by key into Map<K, List<T>> partitioningBy(predicate) | Splits into Map<Boolean, List<T>> counting() | Counts elements (used as downstream collector with groupingBy) summingInt/Long/Double() | Sums a numeric field averagingInt/Long/Double() | Averages a numeric field maxBy(comparator) / minBy(comparator) | Max/Min wrapped in Optional as a downstream collector mapping(fn, downstream) | Transforms elements before collecting further collectingAndThen(collector, fn) | Post-processes the collected result, e.g. wraps in Collections.unmodifiableList()

Optional + Stream combination

List<String> emails = employees.stream() .map(Employee::getEmail) .flatMap(Optional::stream) // drops empty Optionals, flattens present ones .toList(); String email = employee.getEmail() .orElse("N/A"); // or employee.getEmail().orElseThrow(...) when absence is exceptional

Parallel Stream best practices

  • Only use for large, CPU-bound operations — parallelStream is not automatically faster; benchmark before and after.
  • Do not use with blocking calls (DB, HTTP, file I/O) inside the pipeline.
  • Avoid mutating shared state inside a parallel stream.
  • Use a custom ForkJoinPool for better control instead of the common pool when running alongside other parallel work:
ForkJoinPool pool = new ForkJoinPool(8); List<Result> results = pool.submit(() -> items.parallelStream() .map(this::expensiveCompute) .toList() ).join();

When NOT to use streams

  • Extremely complex logic with many nested conditions.
  • Performance-critical hot paths with primitive loops.
  • Side effects or mutating external state inside a stream.
  • Needs fine-grained control flow (early break with return value).
  • Very large data + nested streams inside filters.

Performance notes

  • parallelStream() helps only for CPU-bound, large datasets; not suitable for I/O-bound or blocking operations.
  • Avoid boxing/unboxing overhead; prefer primitive streams (IntStream, LongStream, DoubleStream).
  • Ordering (sorted, distinct, limit) adds overhead.
  • Measure before/after using JMH or load tests.

Production tips

  • Prefer immutable results with toList() when mutation isn't needed.
  • Use explicit comparators for sorting.
  • Use mapToInt, mapToLong, or mapToDouble for numeric aggregation.
  • Be careful with Optional.get(); prefer orElse, orElseThrow, or flatMap(Optional::stream).
  • Avoid nested streams inside filters for large datasets; precompute lookup sets or maps.

Common pitfalls

  • Mutating external state inside streams.
  • Using parallelStream() for small or I/O-bound datasets.
  • Ignoring Optional (using .get() without checking presence).
  • Forgetting the merge function in toMap() when keys can collide.
  • Nesting streams inside filters on large collections.
  • Assuming streams are always faster than a plain loop.

Let's see some examples

public class Employee { private int id; private String name; private int age; private String gender; private String department; private int yearOfJoining; private double salary; // constructor, getters, setters and toString() }

Let's take some scenarios and implement the same using Streams

package com.praveen.streams.employee; import com.praveen.streams.model.Employee; import java.time.LocalDate; import java.util.*; import java.util.stream.Collectors; public class EmployeeStreamDemo { public static List<Employee> createEmployeeList() { return List.of( new Employee(1332, "Ravindra Duggempudi", 35, "Male", "Account And Finance", 2010, 27000.0), new Employee(2211, "Kalpana Chavala", 32, "Female", "HR", 2011, 25000.0), new Employee(3311, "Ravindra Mulakala", 29, "Male", "Infrastructure", 2012, 18000.0), new Employee(4322, "Geetha Sabesan", 27, "Female", "HR", 2013, 22700.0), new Employee(5687, "Praneeth Vishnubhotla", 28, "Male", "Product Development", 2014, 32500.0), new Employee(6543, "Renuga Maharshi", 27, "Female", "Infrastructure", 2014, 15700.0), new Employee(7654, "Praveen Oruganti", 31, "Male", "Product Development", 2015, 34500.0), new Employee(8321, "Ganesh Bendandi", 38, "Male", "Security And Transport", 2015, 11000.5), new Employee(9867, "Kiran Badugu", 25, "Male", "Sales And Marketing", 2015, 13500.0), new Employee(10212, "Swati Ravula", 24, "Female", "Sales And Marketing", 2016, 11500.0), new Employee(11213, "Satheesh Nair", 43, "Male", "Security And Transport", 2016, 10500.0), new Employee(12321, "Gopi Pavurala", 25, "Male", "Product Development", 2016, 28200.0), new Employee(13121, "Jyothi Kadinda", 27, "Female", "Account And Finance", 2016, 21300.0), new Employee(14131, "Bhavani Vanish", 24, "Female", "Sales And Marketing", 2017, 10700.5), new Employee(15131, "Pradeep Rajulu", 23, "Male", "Infrastructure", 2019, 12700.0), new Employee(16311, "Manish Pandey", 26, "Female", "Product Development", 2022, 28900.0), new Employee(17342, "Kohli Virat", 31, "Male", "Product Development", 2023, 35700.0) ); } public static void main(String[] args) { List<Employee> employeeList = createEmployeeList(); System.out.println("\nQ 1 : How many male and female employees are there in the organization?"); employeeList.stream() .collect(Collectors.groupingBy(Employee::getGender, Collectors.counting())) .forEach((gender, count) -> System.out.printf("Gender: %s, Count: %d%n", gender, count)); System.out.println("\nQ 2 : Print the name of all departments in the organization?"); employeeList.stream() .map(Employee::getDepartment) .collect(Collectors.toSet()) .forEach(System.out::println); System.out.println("\nQ 3 : What is the average age of male and female employees?"); employeeList.stream() .collect(Collectors.groupingBy(Employee::getGender, Collectors.averagingInt(Employee::getAge))) .forEach((gender, avgAge) -> System.out.printf("Gender: %s, Average Age: %.2f%n", gender, avgAge)); System.out.println("\nQ 4 : Get the details of the highest paid employee in the organization?"); employeeList.stream() .max(Comparator.comparingDouble(Employee::getSalary)) .ifPresent(employee -> System.out.println("Highest Paid Employee: " + employee.getName())); System.out.println("\nQ 5 : Get the names of all employees who have joined after 2015?"); employeeList.stream() .filter(e -> e.getYearOfJoining() > 2015) .map(Employee::getName) .forEach(System.out::println); System.out.println("\nQ 6 : Count the number of employees in each department?"); employeeList.stream() .collect(Collectors.groupingBy(Employee::getDepartment, Collectors.counting())) .forEach((department, count) -> System.out.printf("Department: %s, Employee Count: %d%n", department, count)); System.out.println("\nQ 7 : What is the average salary of each department?"); employeeList.stream() .collect(Collectors.groupingBy(Employee::getDepartment, Collectors.averagingDouble(Employee::getSalary))) .forEach((department, avgSalary) -> System.out.printf("Department: %s, Average Salary: %.2f%n", department, avgSalary)); System.out.println("\nQ 8 : Get the details of the youngest male employee in the product development department?"); employeeList.stream() .filter(e -> e.getGender().equals("Male") && e.getDepartment().equals("Product Development")) .min(Comparator.comparingInt(Employee::getAge)) .ifPresentOrElse( employee -> System.out.println("Youngest Male Employee: " + employee), () -> System.out.println("No male employees found in the Product Development department.") ); System.out.println("\nQ 9 : Who has the most working experience in the organization?"); employeeList.stream() .max(Comparator.comparingInt(Employee::getYearOfJoining)) .ifPresentOrElse( employee -> System.out.println("Employee with most working experience: " + employee), () -> System.out.println("No employees found.") ); System.out.println("\nQ 10 : How many male and female employees are there in the sales and marketing team?"); employeeList.stream() .filter(e -> "Sales And Marketing".equals(e.getDepartment())) // Using "equals" on the constant string for safety .collect(Collectors.groupingBy(Employee::getGender, Collectors.counting())) .forEach((gender, count) -> System.out.printf("Gender: %s, Count: %d%n", gender, count)); System.out.println("\nQ 11 : What is the average salary of male and female employees?"); employeeList.stream() .collect(Collectors.groupingBy(Employee::getGender, Collectors.averagingDouble(Employee::getSalary))) .forEach((gender, avgSalary) -> System.out.printf("Gender: %s, Average Salary: %.2f%n", gender, avgSalary)); System.out.println("\nQ 12 : List down the names of all employees in each department?"); employeeList.stream() .collect(Collectors.groupingBy(Employee::getDepartment)) .forEach((department, employees) -> { System.out.println("Department: " + department); employees.stream().map(Employee::getName).forEach(name -> System.out.println(" - " + name)); }); System.out.println("\nQ 13 : What is the average salary and total salary of the whole organization?"); DoubleSummaryStatistics salaryStatistics = employeeList.stream() .collect(Collectors.summarizingDouble(Employee::getSalary)); System.out.printf("Average Salary = %.2f and Total Salary = %.2f%n", salaryStatistics.getAverage(), salaryStatistics.getSum()); System.out.println("\nQ 14 : Separate the employees who are younger or equal to 25 years from those employees who are older than 25 years."); Map<Boolean, List<Employee>> partitionedEmployees = employeeList.stream() .collect(Collectors.partitioningBy(e -> e.getAge() <= 25)); System.out.println("Employees 25 or younger:"); partitionedEmployees.get(true).forEach(e -> System.out.println(" - " + e.getName())); System.out.println("Employees older than 25:"); partitionedEmployees.get(false).forEach(e -> System.out.println(" - " + e.getName())); System.out.println("\nQ 15 : Who is the oldest employee in the organization? What is his age and which department he belongs to?"); employeeList.stream() .max(Comparator.comparingInt(Employee::getAge)) .ifPresentOrElse(oldestEmployee -> System.out.printf("Name: %s, Age: %d, Department: %s%n", oldestEmployee.getName(), oldestEmployee.getAge(), oldestEmployee.getDepartment()), () -> System.out.println("No employees found.") ); System.out.println("\nQ 16 : Find 3rd Highest salary?"); Optional<Double> thirdHighestSalary = employeeList.stream() .map(Employee::getSalary) .distinct() .sorted(Comparator.reverseOrder()) .skip(2) .findFirst(); thirdHighestSalary.ifPresentOrElse( salary -> System.out.println("3rd Highest Salary: " + salary), () -> System.out.println("There are fewer than 3 unique salaries.") ); System.out.println("\nQ 17 : Fetch the Employees whose age is greater than 30?"); employeeList.stream() .filter(emp -> emp.getAge() > 30) .forEach(emp -> System.out.println(emp.getName() + " (Age: " + emp.getAge() + ")")); System.out.println("\nQ 18 : Count number of employees with age 25?"); long count = employeeList.stream() .filter(emp -> emp.getAge() == 25) .count(); System.out.println(count); System.out.println("\nQ 19 : Fetch the employee details with name Gopi Pavurala?"); employeeList.stream() .filter(emp -> emp.getName().equalsIgnoreCase("Gopi Pavurala")) .findFirst() .ifPresentOrElse( emp -> System.out.println(emp), () -> System.out.println("No employee found with the name Gopi Pavurala") ); System.out.println("\nQ 20 : Find the maximum age of the employee?"); employeeList.stream() .mapToInt(Employee::getAge) .max() .ifPresentOrElse( maxAge -> System.out.println(maxAge), () -> System.out.println("No employees found") ); System.out.println("\nQ 21 : Sort all the employees on the basis of Age?"); employeeList.stream() .sorted(Comparator.comparingInt(Employee::getAge)) .forEach(emp -> System.out.println(emp)); System.out.println("\nQ 22 : Join all the employee names with ,"); String result = employeeList.stream() .map(Employee::getName) .collect(Collectors.joining(", ")); System.out.println(result); System.out.println("\nQ 23 : Find the sum of all employees' salary?"); double totalSalary = employeeList.stream() .mapToDouble(Employee::getSalary) .sum(); System.out.printf("Total Salary of all employees: %.2f\n", totalSalary); System.out.println("\nQ 24 : Group them by Employee name?"); Map<String, List<Employee>> groupedByName = employeeList.stream() .collect(Collectors.groupingBy(Employee::getName)); groupedByName.forEach((name, employees) -> System.out.println("Name: " + name + " -> " + employees.size() + " employee(s)") ); System.out.println("\nQ 25 : Average Salaries based on department?"); Map<String, Double> avgSalaryByDepartment = employeeList.stream() .collect(Collectors.groupingBy(Employee::getDepartment, Collectors.averagingDouble(Employee::getSalary))); avgSalaryByDepartment.forEach((department, avgSalary) -> System.out.println("Department: " + department + " -> Average Salary: " + avgSalary)); System.out.println("\nQ 26 : Find the Second Highest Salary"); employeeList.stream() .map(Employee::getSalary) .sorted(Comparator.reverseOrder()) .distinct() .skip(1) .findFirst() .ifPresent(System.out::println); System.out.println("\nQ 27 : Find All Employees Who Have Worked in the Organization for More Than 5 Years"); employeeList.stream() .filter(e -> LocalDate.now().getYear() - e.getYearOfJoining() > 5) .forEach(e -> System.out.println(e.getName())); System.out.println("\nQ 28 : Salary histogram - bucket employees into LOW/MEDIUM/HIGH salary bands"); Map<String, Long> salaryBuckets = employeeList.stream() .collect(Collectors.groupingBy(e -> { if (e.getSalary() < 15000) return "LOW"; if (e.getSalary() < 30000) return "MEDIUM"; return "HIGH"; }, Collectors.counting())); System.out.println(salaryBuckets); System.out.println("\nQ 29 : Find the longest employee name"); employeeList.stream() .map(Employee::getName) .max(Comparator.comparingInt(String::length)) .ifPresent(System.out::println); System.out.println("\nQ 30 : Employee name frequency (first names)"); employeeList.stream() .map(e -> e.getName().split(" ")[0]) .collect(Collectors.groupingBy(name -> name, Collectors.counting())) .forEach((name, count) -> System.out.println(name + ": " + count)); System.out.println("\nQ 31 : Department with the maximum number of employees"); employeeList.stream() .collect(Collectors.groupingBy(Employee::getDepartment, Collectors.counting())) .entrySet().stream() .max(Map.Entry.comparingByValue()) .ifPresent(entry -> System.out.println(entry.getKey() + " -> " + entry.getValue())); System.out.println("\nQ 32 : Nth highest salary (generalized, n = 3)"); int n = 3; employeeList.stream() .map(Employee::getSalary) .distinct() .sorted(Comparator.reverseOrder()) .skip(n - 1) .findFirst() .ifPresentOrElse( salary -> System.out.println(n + "th Highest Salary: " + salary), () -> System.out.println("There are fewer than " + n + " unique salaries.") ); System.out.println("\nQ 33 : Sort employees by department, then by name (multiple comparators)"); employeeList.stream() .sorted(Comparator.comparing(Employee::getDepartment) .thenComparing(Employee::getName)) .forEach(e -> System.out.println(e.getDepartment() + " - " + e.getName())); } }