When, in the course of human events, it becomes necessary to read a text file, you should consider using a Stream.

Why? Because a Stream gives you the opportunity to perform some on-the-fly operations that go beyond simple traversal.

In this article, we’ll look at how you can use a Stream (with some other Java 8 conventions) to read and parse a text file. As is usually the case, you can view the code on GitHub.

 

The Use Case

Let’s say you’re running an e-commerce site that sells fishing tackle online. You’ve received some new fishing lures that you’ll add to your inventory.

The vendor was kind enough to send you a semicolon-delimited text file that contains important information about each lure. Each line in the file identifies the lure name, its type (inshore, nearshore, or offshore), and the suggested retail price.

Here’s what that file looks like:

Bizzy Bee;Inshore;5.99
Mister Troller;Nearshore;7.99
Squid Swimmer;Offshore;11.99
Flatfish Forever;Offshore;12.99
Little Minnow Swimmer;Inshore;5.99
Shiny Sliver;Nearshore;8.99

 
Now, it’s up to you to parse that file and persist the relevant info to your database. For that, you’ll use a Stream.

 

Read Every Line in the File

Start off by doing things the easy way. Write a Java application that reads every line in the file.

Here’s what that code looks like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class ReadFile {
 
    private static final String INPUT_FILE = "./input/lures.txt";
     
    public static void main(String[] args) {
        try (Stream<String> stream = Files.lines(Paths.get(INPUT_FILE))) {
            stream.forEach(line -> {
                String[] parts = line.split(";");
                String lureName = parts[0];
                String lureType = parts[1];
                String lurePrice = parts[2];
                 
                System.out.println("Found: " + lureName + " (" + lureType + ") $" + lurePrice );
            });
        } catch (IOException e) {
            System.err.println("Problem reading file " + e.getMessage());
            e.printStackTrace();
        }
    }
}

Right off the bat, the class defines the location of the file in INPUT_FILE.

The executable part of the code starts off with a try-with-resources block. There’s quite a bit of magic happening between those parentheses.

First, the application uses Paths.get to convert the file path to a Path object. That’s how Java will access the input file.

Next, the code uses Files.lines to convert the lines in the input file to a Stream object.

Note that Stream is using type-safety. You can see that with the Stream<String> declaration.

Keep in mind that because the code is using the try-with-resources statement, there’s no need to close any streams. That’s handled implicitly.

The first line within the try block is the declaration of a loop with forEach. As you can plainly tell from the notation, it’s iterating through each line in the file.

The code is using a lambda expression to parse the lines. That’s the “arrow” you see right after the line variable in the forEach declaration.

The parsing itself happens within the inner curly braces. That part isn’t too complicated — the code is just splitting the line by semicolons and setting variables for the lure name, type, and price.

Finally, the application prints out the contents of the line in a way that’s readable to humans. That output looks like this:

Found: Bizzy Bee (Inshore) $5.99
Found: Mister Troller (Nearshore) $7.99
Found: Squid Swimmer (Offshore) $11.99
Found: Flatfish Forever (Offshore) $12.99
Found: Little Minnow Swimmer (Inshore) $5.99
Found: Shiny Sliver (Nearshore) $8.99

 
That’s pretty straightforward. Now, let’s take it up a notch.

 

Reading the File With a Filter

Let’s say you have a new use case: you want to parse the file as before, but you only want to read inshore lures. Fortunately, the Stream class makes that easy.

Here’s the code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ReadFileFiltered {
 
    private static final String INPUT_FILE = "./input/lures.txt";
     
    public static void main(String[] args) {
        try (Stream<String> stream = Files.lines(Paths.get(INPUT_FILE))) {
                 
            stream.filter(line -> line.indexOf("Inshore") > -1)
                .forEach(line -> {
                    String[] parts = line.split(";");
                    String lureName = parts[0];
                    String lureType = parts[1];
                    String lurePrice = parts[2];
 
                    System.out.println("Found: " + lureName + " (" + lureType + ") $" + lurePrice );
                });
        } catch (IOException e) {
            System.err.println("Problem reading file " + e.getMessage());
            e.printStackTrace();
        }
    }
}

As you can see, the code is strikingly similar to the previous block. In this case, though, there’s a filter added before the forEach loop.

That filter simply checks for the existence of the word “Inshore” within the line. If that word exists, then the line will be processed. Otherwise, it will be skipped.

Note that the filter() method returns a Stream object. In this case, it’s the filtered Stream that only includes inshore lures. That’s why the code adds the forEach statement right after the filter() method.

From there, everything is just like before. Once the stream is filtered, the code parses each line just as it did in the previous block.

Here’s what the output looks like:

Found: Bizzy Bee (Inshore) $5.99
Found: Little Minnow Swimmer (Inshore) $5.99

 

Next, let’s look at something else.

 

Reading the File With BufferedReader

There is yet another way that you can process the file: with BufferedReader. Here’s what that code looks like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ReadFileBufferedReader {
 
    private static final String INPUT_FILE = "./input/lures.txt";
     
    public static void main(String[] args) {
        try (BufferedReader br = Files.newBufferedReader(Paths.get(INPUT_FILE))) {   
            Stream<String> stream = br.lines();
             
            stream.forEach(line -> {
                String[] parts = line.split(";");
                String lureName = parts[0];
                String lureType = parts[1];
                String lurePrice = parts[2];
 
                System.out.println("Found: " + lureName + " (" + lureType + ") $" + lurePrice );
            });
        } catch (IOException e) {
            System.err.println("Problem reading file " + e.getMessage());
            e.printStackTrace();
        }
    }
}

As you can see, the BufferedReader object is instantiated within the try-with-resources declaration.

Inside the block itself, the code uses BufferedReader to create a Stream object.

The beauty here is the use of the lines() method on the BufferedReader object. That’s new since Java 8, and offers a great deal of convenience.

After that, everything is just like you saw in the last two code blocks.

The output looks like this:

Found: Bizzy Bee (Inshore) $5.99
Found: Mister Troller (Nearshore) $7.99
Found: Squid Swimmer (Offshore) $11.99
Found: Flatfish Forever (Offshore) $12.99
Found: Little Minnow Swimmer (Inshore) $5.99
Found: Shiny Sliver (Nearshore) $8.99

 

Wrapping It Up

It’s easy to do things the old way. Sometimes, it’s too easy.

When it comes to reading files, though, you can tighten up your code quite a bit by using some of the more recent additions to the Java programming language. Consider using Stream objects to parse files in your own code going forward

Feel free to check out the code for this tutorial on GitHub.

Have fun!