In this tutorial, I will demonstrate how to find the maximum and minimum values in a List of integers using the Java Stream API. This is a common Java coding interview question that helps you understand stream operations, comparators, and Optional values.
Finding the largest and smallest elements is a basic operation when working with numerical data. For example, you might need to find the highest salary, lowest product price, maximum test score, or minimum temperature in a collection.
Java Streams provide the max() and min() terminal operations to find these values. You can also convert a Stream of Integer objects into an IntStream using mapToInt() and then use the corresponding primitive-stream methods.
In this tutorial, you will learn three approaches with complete Java programs, sample outputs, step-by-step explanations, time complexity analysis, and important edge cases.
1. Problem Statement
Write a Java program to find the maximum and minimum values in a List of integers using the Stream API.
Example:
Input: [45, 12, 78, 34, 89, 23, 56] Output: Maximum Value: 89 Minimum Value: 12
Explanation: The largest element in the list is 89, and the smallest element is 12.
Another Example with Negative Numbers
Input: [-20, -5, -100, -1, -50] Output: Maximum Value: -1 Minimum Value: -100
Notice that -1 is the maximum value because it is greater than the other negative numbers.
2. Method 1: Find Maximum and Minimum Using Stream.max() and Stream.min()
The most straightforward approach is to use the max() and min() methods provided by the Stream API.
Both methods require a Comparator to determine the ordering of elements.
For integers, you can use Comparator.naturalOrder(), which compares values in ascending numerical order.
Java Program Using max() and min()
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
public class FindMaxMinUsingStreams {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(
45, 12, 78, 34, 89, 23, 56
);
// Find maximum value
Optional<Integer> max = numbers.stream()
.max(Comparator.naturalOrder());
// Find minimum value
Optional<Integer> min = numbers.stream()
.min(Comparator.naturalOrder());
System.out.println("Original List: " + numbers);
max.ifPresent(value ->
System.out.println("Maximum Value: " + value));
min.ifPresent(value ->
System.out.println("Minimum Value: " + value));
}
}
Output
Original List: [45, 12, 78, 34, 89, 23, 56] Maximum Value: 89 Minimum Value: 12
3. Step-by-Step Explanation
Step 1: Create a List of Integers
List<Integer> numbers = Arrays.asList(
45, 12, 78, 34, 89, 23, 56
);
This statement creates a List containing seven integers.
The list contains values in an unsorted order.
Step 2: Find the Maximum Value
Optional<Integer> max = numbers.stream()
.max(Comparator.naturalOrder());
The stream() method creates a sequential stream from the list.
The max() terminal operation identifies the largest value according to the supplied Comparator.
Here, Comparator.naturalOrder() uses the natural ascending order of Integer values.
For the given list, the maximum value is 89.
Step 3: Find the Minimum Value
Optional<Integer> min = numbers.stream()
.min(Comparator.naturalOrder());
The min() method identifies the smallest element according to the same Comparator.
For this list, the minimum value is 12.
Step 4: Display the Results
max.ifPresent(value ->
System.out.println("Maximum Value: " + value));
min.ifPresent(value ->
System.out.println("Minimum Value: " + value));
Both max() and min() return an Optional<Integer>.
An Optional represents a value that may or may not be present.
This is useful because an empty list has no maximum or minimum element.
The ifPresent() method executes the printing operation only when the Optional contains a value.
4. Method 2: Find Maximum and Minimum Using mapToInt()
Another approach is to convert the Stream of Integer objects into a primitive IntStream.
The mapToInt() method performs this conversion.
Unlike Stream.max() and Stream.min(), the corresponding IntStream methods do not require an explicit Comparator.
Java Program Using mapToInt()
import java.util.Arrays;
import java.util.List;
import java.util.OptionalInt;
public class FindMaxMinUsingIntStream {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(
45, 12, 78, 34, 89, 23, 56
);
// Find maximum using IntStream
OptionalInt max = numbers.stream()
.mapToInt(Integer::intValue)
.max();
// Find minimum using IntStream
OptionalInt min = numbers.stream()
.mapToInt(Integer::intValue)
.min();
System.out.println("Original List: " + numbers);
max.ifPresent(value ->
System.out.println("Maximum Value: " + value));
min.ifPresent(value ->
System.out.println("Minimum Value: " + value));
}
}
Output
Original List: [45, 12, 78, 34, 89, 23, 56] Maximum Value: 89 Minimum Value: 12
How mapToInt() Works
Consider this statement:
OptionalInt max = numbers.stream()
.mapToInt(Integer::intValue)
.max();
It performs three operations:
numbers.stream()creates a Stream of Integer objects.mapToInt(Integer::intValue)converts those objects into primitive integer values.max()finds the largest primitive integer value.
The Integer::intValue expression is a method reference that extracts the primitive int value from each Integer object.
The IntStream.max() and IntStream.min() methods return OptionalInt, which is designed to represent optional primitive integer values.
Why Use IntStream?
Using IntStream is useful when performing numerical operations such as:
- Finding the maximum value
- Finding the minimum value
- Calculating the sum
- Calculating the average
- Counting elements
Primitive streams provide specialized operations for numerical data and avoid creating additional boxed Integer values during primitive computations.
5. Method 3: Find Maximum and Minimum Using Stream.reduce()
The reduce() operation combines stream elements into a single result.
You can use it with Integer::max and Integer::min to find the largest and smallest values.
This approach is useful for understanding reduction operations in the Java Stream API.
Java Program Using reduce()
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public class FindMaxMinUsingReduce {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(
45, 12, 78, 34, 89, 23, 56
);
// Find maximum using reduce()
Optional<Integer> max = numbers.stream()
.reduce(Integer::max);
// Find minimum using reduce()
Optional<Integer> min = numbers.stream()
.reduce(Integer::min);
System.out.println("Original List: " + numbers);
max.ifPresent(value ->
System.out.println("Maximum Value: " + value));
min.ifPresent(value ->
System.out.println("Minimum Value: " + value));
}
}
Output
Original List: [45, 12, 78, 34, 89, 23, 56] Maximum Value: 89 Minimum Value: 12
How reduce() Works
The expression:
.reduce(Integer::max)
combines elements by repeatedly selecting the larger of two values.
Similarly:
.reduce(Integer::min)
combines elements by repeatedly selecting the smaller value.
These operations return Optional results because the input stream may be empty.
6. Dry Run: Finding Maximum and Minimum Values
Let's understand the process using this input:
[45, 12, 78, 34, 89]
Conceptually, a sequential maximum/minimum calculation starts with the first element as both the current maximum and minimum.
It then compares each remaining element with the current values.
| Step | Current Number | Current Maximum | Current Minimum |
|---|---|---|---|
| 1 | 45 | 45 | 45 |
| 2 | 12 | 45 | 12 |
| 3 | 78 | 78 | 12 |
| 4 | 34 | 78 | 12 |
| 5 | 89 | 89 | 12 |
Final result:
Maximum Value: 89 Minimum Value: 12
This table illustrates the comparison logic used to find the largest and smallest values. Each of the earlier programs performs separate maximum and minimum terminal operations.
7. Find Maximum and Minimum in a Single Stream Traversal
The previous examples create separate streams to find maximum and minimum values.
If you need both values, you can use IntSummaryStatistics to calculate multiple statistics during a single stream traversal.
This is particularly useful for applications that need additional information, such as the count, sum, and average.
Java Program Using IntSummaryStatistics
import java.util.Arrays;
import java.util.IntSummaryStatistics;
import java.util.List;
public class FindMaxMinUsingStatistics {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(
45, 12, 78, 34, 89, 23, 56
);
IntSummaryStatistics stats = numbers.stream()
.mapToInt(Integer::intValue)
.summaryStatistics();
System.out.println("Maximum Value: "
+ stats.getMax());
System.out.println("Minimum Value: "
+ stats.getMin());
System.out.println("Count: "
+ stats.getCount());
System.out.println("Sum: "
+ stats.getSum());
System.out.println("Average: "
+ stats.getAverage());
}
}
Output
Maximum Value: 89 Minimum Value: 12 Count: 7 Sum: 337 Average: 48.142857142857146
The summaryStatistics() operation calculates the minimum, maximum, count, sum, and average in one traversal.
Important: For an empty stream, IntSummaryStatistics reports a maximum of Integer.MIN_VALUE and a minimum of Integer.MAX_VALUE. Always check stats.getCount() > 0 before treating those values as actual results.
8. Time and Space Complexity
Let's compare the approaches discussed in this tutorial.
| Approach | Time Complexity | Auxiliary Space | Traversals |
|---|---|---|---|
| Stream.max() and min() | O(n) | O(1) | 2 |
| IntStream.max() and min() | O(n) | O(1) | 2 |
| Stream.reduce() | O(n) | O(1) | 2 |
| IntSummaryStatistics | O(n) | O(1) | 1 |
Here, n represents the number of elements in the list.
Each approach must examine the elements to determine the largest or smallest value, so the total time complexity is O(n).
For sequential streams, these operations use constant auxiliary storage. The IntSummaryStatistics approach calculates both values in one traversal.
9. Handling Important Edge Cases
Case 1: Empty List
Input: [] Result: No maximum or minimum value exists.
For an empty list, Stream.max() and Stream.min() return empty Optional objects.
You can handle this case using isPresent(), ifPresent(), or orElse().
For example:
Optional<Integer> max = numbers.stream()
.max(Comparator.naturalOrder());
if (max.isPresent()) {
System.out.println("Maximum Value: " + max.get());
} else {
System.out.println("List is empty");
}
Case 2: List Contains One Element
Input: [42] Output: Maximum Value: 42 Minimum Value: 42
When a list contains one element, that value is both the maximum and minimum.
Case 3: List Contains Duplicate Elements
Input: [10, 20, 30, 30, 20, 10] Output: Maximum Value: 30 Minimum Value: 10
Duplicate elements do not affect the maximum or minimum values.
Case 4: All Values Are Equal
Input: [7, 7, 7, 7] Output: Maximum Value: 7 Minimum Value: 7
Case 5: All Values Are Negative
Input: [-50, -10, -100, -25] Output: Maximum Value: -10 Minimum Value: -100
The Stream API correctly compares negative integers using their natural ordering.
Case 6: List Contains Null Values
The earlier examples assume that the List contains non-null Integer values.
If null elements are possible, filter them before finding the maximum and minimum.
import java.util.Objects;
Optional<Integer> max = numbers.stream()
.filter(Objects::nonNull)
.max(Comparator.naturalOrder());
Optional<Integer> min = numbers.stream()
.filter(Objects::nonNull)
.min(Comparator.naturalOrder());
This removes null elements from the stream before comparisons take place.
10. Common Mistakes to Avoid
Mistake 1: Calling get() on an Empty Optional
The max() and min() methods return Optional results because the stream may be empty.
Calling get() on an empty Optional throws a NoSuchElementException.
Prefer ifPresent(), orElse(), or another appropriate Optional-handling method.
Mistake 2: Reusing the Same Stream
A Stream cannot be reused after a terminal operation.
For example, you cannot call both max() and min() on the same Stream instance after one operation has consumed it.
Create a separate stream for each operation or use summaryStatistics() to calculate both values in one traversal.
Mistake 3: Confusing Comparator Ordering
For integer values, Comparator.naturalOrder() uses ascending order.
Therefore:
max(Comparator.naturalOrder())returns the largest integer.min(Comparator.naturalOrder())returns the smallest integer.
Using Comparator.reverseOrder() reverses the comparison order and changes which element is considered maximum or minimum by that Comparator.
Mistake 4: Assuming max() Sorts the Collection
The max() and min() methods identify extreme values without sorting the entire list.
Sorting is unnecessary when your goal is only to find the maximum or minimum value.
11. Frequently Asked Questions
Q1. How do I find the maximum value in a List using Java Streams?
Use max() with Comparator.naturalOrder():
Optional<Integer> max = numbers.stream()
.max(Comparator.naturalOrder());
Q2. How do I find the minimum value in a List using Java Streams?
Use min() with Comparator.naturalOrder():
Optional<Integer> min = numbers.stream()
.min(Comparator.naturalOrder());
Q3. Can I find maximum and minimum values without using a Comparator?
Yes. Convert the Stream of Integer values into an IntStream using mapToInt(). The IntStream max() and min() methods do not require a Comparator.
Q4. Why do Stream.max() and Stream.min() return Optional?
An empty stream has no maximum or minimum element. Optional provides a way to represent the possible absence of a value.
Q5. What is the time complexity of finding maximum and minimum values using Streams?
The time complexity is O(n), where n represents the number of elements.
Q6. Can Java Streams find maximum and minimum values in a single traversal?
Yes. For integer data, use mapToInt() followed by summaryStatistics().
Q7. Do max() and min() modify the original List?
No. They are terminal operations that return results without changing the original list.
Q8. Which approach is best for Java coding interviews?
The Stream.max() and Stream.min() methods are the easiest to explain. You should also understand mapToInt() and summaryStatistics(), particularly when working with numerical collections.
12. Conclusion
In this tutorial, you learned how to find the maximum and minimum values in a List using the Java Stream API.
- Stream.max() and Stream.min(): Find extreme values using a Comparator.
- IntStream.max() and IntStream.min(): Find extreme values using primitive integer streams.
- Stream.reduce(): Demonstrates how reduction operations can calculate maximum and minimum values.
- IntSummaryStatistics: Calculates minimum, maximum, count, sum, and average in a single traversal.
For beginners and Java coding interviews, I recommend starting with Stream.max() and Stream.min(). These methods clearly express the intention of the program and are easy to understand.
If your application needs both maximum and minimum values, especially along with other statistics, summaryStatistics() is a useful alternative.
13. Official Java Documentation
For additional information about the Java APIs used in this tutorial, refer to the official Oracle documentation:
- Java Stream Interface — Documentation for
max(),min(),mapToInt(), andreduce(). - Java IntStream Interface — Documentation for primitive integer stream operations.
- Java Comparator Interface — Documentation for natural and custom ordering.
- Java Optional Class — Documentation for handling optional object values.
- Java OptionalInt Class — Documentation for optional primitive integer values.
- Java IntSummaryStatistics Class — Documentation for calculating numerical statistics.
Continue Learning with Java Guides
Want to take your programming skills to the next level? Explore my practical Udemy courses on Java, Spring Boot, Microservices, Full-Stack Development, Docker, and Generative AI. You can also follow my structured learning roadmaps to become a professional developer.
By Ramesh Fadatare - Udemy Instructor Partner
Comments
Post a Comment
Leave Comment