DEV Community

Priyank Bhardwaj
Priyank Bhardwaj

Posted on

Streams with Optional & File I/O in Java

Java Streams become even more powerful when combined with Optional and file handling APIs. In this article, we’ll explore how to safely handle missing values using Optional, process files efficiently with Files.lines(), and build a practical example that counts unique words in a text file.

If you’ve followed the earlier parts of this series, you already know how Streams help process collections in a functional and readable way. Now it’s time to apply those concepts to real-world scenarios.

Why Combine Streams, Optional, and File I/O?

In modern Java applications, you often:

  • Read large files
  • Process textual data
  • Search for values that may or may not exist
  • Avoid NullPointerException
  • Write concise and clean code

Streams, Optional, and Java NIO APIs work together beautifully to solve these problems.


Using Optional with Streams

Optional<T> is a container object introduced in Java 8 that may or may not contain a value.

Streams often return Optional when the result may be absent.

Common Stream Methods Returning Optional

Method Return Type
findFirst() Optional<T>
findAny() Optional<T>
max() Optional<T>
min() Optional<T>
reduce() Optional<T>

Example: findFirst()

import java.util.List;
import java.util.Optional;

public class OptionalExample {
    public static void main(String[] args) {

        List<String> names = List.of("Aman", "Priyank", "Rahul");

        Optional<String> result = names.stream()
                .filter(name -> name.startsWith("P"))
                .findFirst();

        result.ifPresent(System.out::println);
    }
}
Enter fullscreen mode Exit fullscreen mode

Output:

Priyank
Enter fullscreen mode Exit fullscreen mode

Handling Empty Results Safely

Without Optional, missing values could lead to NullPointerException.

Using orElse()

String value = result.orElse("Not Found");

System.out.println(value);
Enter fullscreen mode Exit fullscreen mode

If no matching element exists, "Not Found" is returned.

Using orElseGet()

String value = result.orElseGet(() -> "Default Value");
Enter fullscreen mode Exit fullscreen mode

Useful when default value creation is expensive.

Using orElseThrow()

String value = result.orElseThrow(
        () -> new RuntimeException("Value not found")
);
Enter fullscreen mode Exit fullscreen mode

Throws an exception if value is absent.


Example: Find Maximum Number

import java.util.List;
import java.util.Optional;

public class MaxExample {
    public static void main(String[] args) {

        List<Integer> numbers = List.of(10, 20, 50, 30);

        Optional<Integer> max = numbers.stream()
                .max(Integer::compareTo);

        max.ifPresent(System.out::println);
    }
}
Enter fullscreen mode Exit fullscreen mode

Output

50
Enter fullscreen mode Exit fullscreen mode

Here, numbers.stream().max() returns an Optional. ifPresent() is another inbuilt function on Optional<T> type value which perform an action (in this case printing the value to console) if some value is returned by the stream.


Reading Files Using Files.lines()

Java NIO introduced the Files.lines() method for efficient file reading.

Instead of loading the entire file into memory, it processes the file lazily line by line using Streams.

Syntax

Files.lines(Path path)
Enter fullscreen mode Exit fullscreen mode

Returns Stream<String>. Each line of the file becomes a stream element.

Basic Example

Suppose sample.txt contains:

Java
Spring Boot
Streams API
Enter fullscreen mode Exit fullscreen mode

import java.nio.file.Files;
import java.nio.file.Paths;

public class FileReadExample {
    public static void main(String[] args) {

        try {
            Files.lines(Paths.get("sample.txt"))
                    .forEach(System.out::println);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Output

Java
Spring Boot
Streams API
Enter fullscreen mode Exit fullscreen mode

Why Files.lines() is Powerful

  • Memory efficient
  • Lazy processing
  • Works well with large files
  • Can directly use Stream operations

Example: Filter Lines

Files.lines(Paths.get("sample.txt"))
        .filter(line -> line.contains("Java"))
        .forEach(System.out::println);
Enter fullscreen mode Exit fullscreen mode

Example: Count Lines

long count = Files.lines(Paths.get("sample.txt"))
        .count();

System.out.println(count);
Enter fullscreen mode Exit fullscreen mode

Real-World Example: Count Unique Words in a Text File

Now let’s combine everything we learned.

We will:

  • Read a text file
  • Split lines into words
  • Convert words to lowercase
  • Remove duplicates
  • Count unique words

Sample file (article.txt)

Java Streams are powerful
Streams make Java code concise
Java is widely used
Enter fullscreen mode Exit fullscreen mode

Program

import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.stream.Stream;

public class UniqueWordCounter {

    public static void main(String[] args) {

        try (Stream<String> lines = Files.lines(Paths.get("article.txt"))){ // Read File as Stream 

            long uniqueWords = lines
                    .flatMap(line -> Stream.of(line.split("\\s+"))) // Convert Lines into Words
                    .map(String::toLowerCase)  // convert the words to lowercase
                    .distinct() // remove duplicates
                    .count();  // count unique words

            System.out.println("Unique Words: " + uniqueWords);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Output

Unique Words: 10
Enter fullscreen mode Exit fullscreen mode

Another Example: Find First Long Word (word with more than 7 characters) Using Optional

import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Optional;
import java.util.stream.Stream;

public class LongWordFinder {

    public static void main(String[] args) {

        try (Stream<String> lines = Files.lines(Paths.get("article.txt"))) {

            Optional<String> word = lines
                    .flatMap(line -> Stream.of(line.split("\\s+")))
                    .filter(w -> w.length() > 7) // filter the words with length greater than 7 characters
                    .findFirst(); // pick the first word among the filtered long words

            System.out.println(
                    word.orElse("No long word found")
            );

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Best Practices

Always Use try-with-resources. Files.lines() opens a file resource. Always close it properly:

try (Stream<String> lines = Files.lines(path)) {
    // process
}
Enter fullscreen mode Exit fullscreen mode

Prefer Stream Operations Over Manual Loops

Instead of:

for(String line : lines)
Enter fullscreen mode Exit fullscreen mode

Use:

lines.filter(...)
     .map(...)
Enter fullscreen mode Exit fullscreen mode

Cleaner and more functional.

Avoid Calling get() on Optional Directly

Bad:

optional.get();
Enter fullscreen mode Exit fullscreen mode

Safer alternatives:

orElse()
orElseThrow()
ifPresent()


Conclusion

Streams are not limited to collections. When combined with Optional and Java NIO file APIs, they become a powerful toolkit for handling real-world data processing tasks efficiently and elegantly.

In this article, we learned how to:

  • Use Optional safely with Streams
  • Read files using Files.lines()
  • Process text data functionally
  • Count unique words in a file

These techniques are commonly used in backend systems, analytics tools, log processing applications, and Java projects.

Top comments (0)