In my previous article, I wrote about the fundamentals of streams in Java 8. Now, let’s augment our skills with some additional information about streams, like how we can chain them, and we can use them to access files.
Chaining Streams
When working with streams, they are often chained together.
Let us see what are the advantages of using chained streams:
- One stream instance leverages another stream instance.
- This creates a higher level of functionality. We can have one stream accessing the data, then we have another stream that takes the results of that and processes more complex functionality.
- This simplifies reusability because you can organize your streams in a way that allows each of them performs a specific job. In this way, they do not need to know each other’s inner workings.
We perform chaining using a constructor. We construct a higher level instance of the stream and then pass an instance of a lower level stream.
A good example of a chained stream is the InputStreamReader
class, which is what we talked about in my previous article.
This class leverages chaining by providing reader behavior over an InputStream
. It translates the binary response to character behavior.
Let us see how it works.
void doChain(InputStream in) throws IOException{ int length; char[] buffer = new char[128]; try(InputStreamReader rdr = new InputStreamReader(in)) { while((length = rdr.read(buffer)) >= 0) { //do something } } }
As you can see, we do not need to care about how the InputStream
works. Whether it is backed by a file or network, it does not matter.
The only thing we know that it gives us binary data, we will pass it to our InputStreamReader
and it converts it and can work with it as a character data.
Notice that we use try-with-resources
here as well. If we close the InputStreamReader
, it automatically closes theInputStream
as well. This a very powerful concept, that you should know about.
File and Buffered Streams
We often use streams for accessing files.
There are several classes for that in the java.io package to use, like:
FileReader
FileWriter
FileInputStream
FileOutputStream
The real thing is that these file streams are deprecated now. Despite that, they are still widely used in codes. So, you probably will face them in the near future, so it’s worth a notation.
Let us look at new ways to interact with files.
Buffered Streams
Buffered Streams are introduced to replace the FileStream
classes in the java.io package. These new Streams are placed under the java.nio package.
It was necessary because direct file access can be inefficient and buffered streams can significantly improve efficiency with the following:
- Buffer content in memory
- Perform reads/writes in large chunks
- Reduce underlying stream interaction
Buffering available for all four stream types:
BufferReader
BufferWriter
BufferedInputStream
BufferedOutputStream
Using them is very straightforward.
try(BufferedReader br = new BufferedReader(new FileReader("file.txt"))){ int value; while((value = br.read()) >= 0) { char charValue = (char)value; //do something } }
Additional benefits to using BufferedStreams
includes:
- It handles linebreaks for various platforms like Windows or Unix
- Uses correct value for the current platform
- The
BufferedWriter
has a method:newLine()
. It will create a new line with the appropriate character. - The
BufferedReader
has a method for line based read:readLine().
Let us see how they work.
BufferedWriter:
void writeData(String[] data) throws IOException { try(BufferedWriter bw = new BufferedWriter(new FileWriter("file.txt"))){ int value; for(String str : data) { bw.write(str); bw.newLine(); } }
BufferedReader:
void readData(String[] data) throws IOException { try(BufferedReader br = new BufferedReader(new FileReader("file.txt"))){ String inValue; while((inValue = br.readLine()) != null) { System.out.println(inValue); } }
The code above will write out the file’s content line by line.
Accessing Files With the java.nio.file package
In Java 8, the java.io.FileXXX streams are deprecated. There is a new package to handle file streams called the java.nio.file package.
The package has several benefits over java.io:
- Better exception reporting
- Greater scalability, they work much better with large files
- More file system feature support
- Simplifies common tasks
Bellow, we will talk about the most fundamental Types In this new package.
Paths and Path Types
Path
- Used to locate a file system item
- It can be a file or directory
Paths
- Used to get the Path objects through static Path factory methods
- It translates a string-based hierarchical path or URI to Path.
Example: Path p = Paths.get(“\\documents\\foo.txt”)
Files Type
- Static methods for interacting with files
- Create, copy, delete, etc…
- Open files streams
newBufferedReader
newBufferedWriter
newInputStream
newOutputStream
- Read/Write file contents
readAllLines
write
Reading Lines With BufferedReader
Let us see some quick example of how you can use it.
void readData(String[] data) throws IOException { try(BufferedReader br = Files.newBufferedReader(Paths.get("data.txt")){ String inValue; while((inValue = br.readLine()) != null) { System.out.println(inValue); } } }
Read All lines
void readAllLines(String[] data) throws IOException { List<String> lines = Files.readAllLines(Paths.get("data.txt")); for(String line:lines) { System.out.println(); } }
File Systems
When we work with files from a Java program, those files are contained within a file system. Most commonly, we use the computer’s default file system.
Java also supports specialized file systems, such as the Zip file system.
Path instances are tied to a file system and thePath
class works only for the default one. So, we need another solution. Fortunately, in the Java.nio package, we have the opportunity to deal with this.
File System Types
FileSystem
- Represents an individual file system
- Factory for
Path
instances
FileSystems
- Used to get the
FileSystem
objects through staticFileSystem
factory methods - Open or create a file system
-
newFileSystem
-
Accessing File Systems
File systems identified by URIs
- Specifics of URI vary greatly among the file systems
- Zip file system uses “jar:file” scheme
- jar:file:/documents/data.zip
File systems support custom properties
- Different for each file system type
- Examples: String encoding, whether to create if it does not exist
Creating a Zip Filesystem
public static void main(String[] args) throws FileNotFoundException, IOException { try (FileSystem zipFileSystem = openZip(Paths.get("data.zip"))){ //pass the Path where we would like to create our FileSystem }catch (Exception e) { System.out.println(e.getClass().getSimpleName() + " - " + e.getLocalizedMessage());; } } private static FileSystem openZip(Path path) throws URISyntaxException, IOException { Map<String, String> properties = new HashMap<>(); properties.put("create", "true"); //set the property to allow creating URI zipUri = new URI("jar:file", path.toUri().getPath(), null); //make a new URI from the path FileSystem zipFileSystem = FileSystems.newFileSystem(zipUri, properties); //create the filesystem return zipFileSystem; }
After the code above, you should see your data.zip file in your directory.

Copying Files to Zip Filesystem
Let us augment the above example with a File copy operation.
In this example, I created a file called file.txt in my project library. We will copy this file to our data.zip Filesystem.

public static void main(String[] args) throws FileNotFoundException, IOException { try (FileSystem zipFileSystem = openZip(Paths.get("data.zip"))){ copyFileToZip(zipFileSystem); //Here we call the file copy }catch (Exception e) { System.out.println(e.getClass().getSimpleName() + " - " + e.getLocalizedMessage());; } } private static FileSystem openZip(Path path) throws URISyntaxException, IOException { Map<String, String> properties = new HashMap<>(); properties.put("create", "true"); URI zipUri = new URI("jar:file", path.toUri().getPath(), null); FileSystem zipFileSystem = FileSystems.newFileSystem(zipUri, properties); return zipFileSystem; } static void copyFileToZip(FileSystem zipFileSystem) throws IOException{ Path sourceFile = FileSystems.getDefault().getPath("file.txt"); //Read the file to copy Path destFile = zipFileSystem.getPath("/fileCopied.txt"); //get the path of the new file Files.copy(sourceFile, destFile);//Copy the file to our zip FileSystem }
After you run the code, you should see the fileCopied.txt int our zip-file. Its context should be the same as in our file.txt.

Summary
In this article, we went further into streams in Java 8. I demonstrated how stream chaining works, as well as how you can deal with files through the new java.nio package. We also touched on why you should use more up-to-date, buffered versions of the Filestreams.
Hope you enjoyed!