Using Java 8 Streams to Find Open Ports
In the last time we saw how to use lambdas as predicates, specifically with the Java 8 Collection#removeIf method in order to remove elements from a map based on the predicate. In this article, we will use a predicate to filter elements from a stream, and combine it with a generator to find the first open port in a specific range.
Use Case
The use case is a (micro)service-based environment where each new service binds to the first open port it finds in a specific port range (49152 to 65535, as defined by IANA). We want to choose a port at random in this range and bind to that port if it is open. If it is not open, we will repeat the process until we find an open port or until we reach a pre-defined number of attempts.
Original Pre-Java 8 Code
Integer findOpenPort(int maxAttempts) {
Integer assignedPort = null;
int count = 0;
while (count < maxAttempts) {
int port = randomPort();
if (portChecker.isAvailable(port)) {
assignedPort = port;
break;
}
count++;
}
return assignedPort;
}
Observations
- Returns an Integer indicating the port or
nullif none is found. - Utilizes mutable variables
assignedPortandcountto store state and track attempts. - Executes a loop while the maximum attempts haven’t been exceeded.
- Uses a port checker to determine availability and breaks if a port is found.
- Uses a ternary expression for return value.
Refactoring with Java 8 Stream API
To refactor using Java 8 streams:
- Generate a sequence of random ports.
- Filter for open ports and return the first open port encountered.
- Return an empty value that indicates no open port was found, represented as an
OptionalInt.
Here is the refactored code:
OptionalInt findOpenPortStream(int maxAttempts) {
return IntStream.generate(this::randomPort)
.filter(portChecker::isAvailable)
.limit(maxAttempts)
.findFirst();
}
Explanation
- The method returns
OptionalInt, providing a clear indication of presence or absence of a value. - Generates an infinite sequence of random integers and limits it to
maxAttempts. - The filter method uses method reference to the
isAvailablemethod. findFirsteffectively “short-circuits,” terminating once a valid port is found.
Advantages of the Functional Approach
- No mutable variables involved, simplifying the logic.
- Declarative nature improves readability.
- Easier to compose with generic methods.
Example of a Re-Usable Method
You could implement a method to find the first random number above two billion within 10 attempts:
OptionalInt result = findFirst(randomSupplier, n -> n > 2000000000, 10).orElse(42);
Conclusion
The functional approach allows composability and keeps the code concise, focusing on the business logic rather than the mechanism. This style can significantly improve readability and maintainability.