Part VIII: Working with Files in Java – Reading from/writing to files + Try-catch and Exceptions

By | July 10, 2021

Table of Contents

Working with files (reading/writing) in Java could be useful to help you store data persistently even after your program has been shut down. If you don’t have a way of persistently/permanently storing your programs data when the program shuts down, it will all reset and you have to start all over the next time you start up your program.

Java has capabilities of working with files (both reading from and writing to), as well as doing more than this, but for the purpose of this article, we will stick to focus on file operations in Java.

There are various classes you can work with in Java when you want to do file operations such as:

Introduction to Input/Output (I/O) Data Streams in Java

Data streams represent a sequence of data that can be either read or written.

In Java our data streams work with 1 item at a time and can be of various data types such as Bytes (files on our computers are stored in bits and bytes (1 byte = 8 bits)), primitive data types, objects or characters.

Input/Output data streams in Java represent either an input source or an output destination.

Our program uses Input Streams to read data from a specified source and an Output Stream to write data in our programs.

Data streams that are opened in Java need to be closed to not negatively affect performance and whatnot unecessarily and release the system resources they occupy when they are not being used. Such is good praxis for working with data streams in Java.

Byte Streams in Java Explained

Byte streams work with Bytes, plain and simple, the files you have on your computer are stored in Bytes, and each Byte consist of 8 bits.

Which means we can use Byte Streams to read from and write to files on our computers using Byte Streams. However my take from the Oracle Documentations footnotes on when to use this is whenever the data you wish to read and write doesn’t match any of the other streams and the most fitting match would be Bytes.

If further curious, you can do some additional investigating and check out Oracle Java Documentations take on Byte Streams here.

Byte Streams use the classes FileInputStream and FileOutputStream which are subclasses from the InputStream and OutputStream classes in Java and specialized for File Input/Output operations.

Character Streams in Java Explained

Character Streams work similarly to the Byte Streams, however it is more specialized for character sets of data – which in Java means that the Character Stream will automatically – without any extra additional efforts from the programmer – translate the data stream to and from the local character set (usually 8-bit ASCII) that the program is working with. This can be great for internationalization efforts further down the road in your development journey.

Character Streams use the classes FileReader and FileWriter which are subclasses from the Reader and Writer classes in Java and specialized for File Input/Output operations.

We can use the Character Stream to read lines from files, and not just Bytes and single characters. To do this we need to use the Java classes BufferedReader and PrintWriter.

The PrintWriter class got methods to write lines one at a time (.println()), while the BufferedReader got methods to read lines one at a time (.readLine()).

We’ll take a deeper dive and more of an in-depth look at the classes BufferedReader and PrintWriter later in this article.

For more information about Character Streams you can check out the Oracle Java Documentation here.

Buffered Streams in Java Explained

Basically the point of Buffered Streams is to provide a more efficient way to handle read and write requests compared to un-buffered streams. This to help you save in on unecessary requests, disk access etc. and thereby also boost performance for your program.

Unbuffered Streams can be converted into Buffered Streams by creating the Un-buffered Streams inside of the BufferedReader and BufferedWriter constructors, like so:

= new BufferedReader(new FileReader("pathToFile.txt"));
= new BufferedWriter(new FileWriter("pathToOutput.txt"));

Buffered stream can be used for Byte Streams: BufferedInputStream and BufferedOutputStream
as well as for Character Streams as you saw above, using: BufferedReader and BufferedWriter.

Buffered Streams don’t write until the Buffer is full, to force the writing of a Buffer one can use its .flush() method, or by invoking Formatting of the stream via the PrintWriter class .println() and .format() methods in Java.

More about working with these Buffered Streams you can find later in this article when demonstrating actually working with files using the various Data Streams explained here.

In the meantime if you want to read up a bit on your own, you can check out Oracle Java Documentation where above Un-buffered -> Buffered example was inspired from.

Data Streams in Java Explained

Technically we have refered to data streams in previous sections of this article where we meant general data streams, where this type of Data Stream is more specific to Binary data such as Primitive data type values (boolean, int, double, char, Strings (even though technically not primitive), etc.)

Data Streams uses the classes DataInputStream and DataOutputStream for its file input/output operations.

Both DataInputStream and DataOutputStream can only be created as wrappers for an already existing Byte Stream.

These two classes provide methods to both read and write for each of Java’s primitive data types that are supported.

To read more about Data Streams, check out the Oracle Java Documentation.

Object Streams in Java Explained

You probably have guessed it by now what Object Stream does, after having read previous sections – it reads and writes object data from/to files.

Object Streams uses the classes ObjectInputStream and ObjectOutputStream for its file input/output operations.

And since Both ObjectInputStream and ObjectOutputStream share the DataInput interface that our previous Data Streams also utilized, this means that our Object Streams can also use those methods to read and write primitive data type values from and to files same way our Data Stream could!

Object Streams uses the methods .readObject(obj) and .writeObject(obj) to read and write objects from and to files.

To read more about Streams in Java, you can check out Oracles Java Documentation for great additional content on the subject matter.

Formatting of data streams in Java

This can be done using either PrintStream which works with Byte Streams, or PrintWriter which works with Character Streams.

Both PrintStream as well as PrintWriter has the same methods for formatting.

PrintStream is the class in Java you might be familiar with from using System.out.

Both classes have access to methods like .println(), .print(), .printf() and .format().

I’ll leave it at that for now since this is not the intended focus of this article, but feel free to check out the Oracle Java Documentation for both of the classes above if you want to learn more details.

Introduction to the Try-catch statement in Java

Since working with Files in Java can be very fickle in that a file can either exist, or not exist when you need it, the best way to handle this is to catch possible Exceptions that may be “thrown” by working with our data stream classes that will be reading from and writing to the file.

Introduction to Exceptions in Java

So what then is an Exception you may ask? An Exception (Exceptional condition) occurs when something disrupts or crashes the normal flow of the program. And when an Exception occurs, an object with information regarding the error that occurred is created and this is called that an Exception “was thrown” – and this object can then later be “caught” in our catch-clause in our Try-Catch statement that you can see below.

The 3 Different types of Exceptions in Java Explained

In Java we have 3 different types of Exceptions that can be thrown, out of which only 1 of them is an Exception that you can control and possibly fix by guiding your users through it (faultily input file name for file to open for example), the other two: Error (class) – external to the program; like system malfunction and RuntimeException (class) – logic errors that require you as developer to fix them rather than handling the Exceptions. And all of their subclasses.

Exception handling features for Object-oriented programming

You can also define your own specific Exception subclass for your particular situation if none of Javas pre-programmed already existing ones suit your needs. More about this will be discussed in later chapter about Object-oriented programming (OOP).

Another thing worth mentioning when coding methods as part of Object-oriented programming (OOP) and a method you create has a Try-Catch in it, you need to add a “throws-clause” for the specific Exception that can be thrown together with the method declaration – but more about this later in our chapter about Object-oriented programming as well.

Examples of common Exceptions that can be thrown in Java

In below table you can see some common examples of Exceptions that can be thrown and what they stand for:

NullPointerException Null snook in where it shouldn’t be – like when an object is expected for example
Exception Any and all Exceptions
IOException Signals failed Input/Output Operation
RuntimeException Like Exception is a superclass for ALL Exceptions (including this one), RuntimeException is a superclass (a.k.a. parent class) to all Runtime specific Exceptions (such as IndexOutOfBoundsException)
IndexOutOfBoundsException Signals that an index for array or similar is out of range, which means the program tries to access values at a specific array index (usually) that doesn’t exist
EOFException Signals the end of a file or end of stream reached (sometimes unexpectedly)
ClassNotFoundException Signals when no definition of a class with a specified name could be found

Understanding Exceptions and which ones can occur, can also help you understand what goes wrong in your program when it crashes, since it often tells you which Exception was thrown as a sort of “report” of what caused the crash.

Useful Exception methods in Java

The Exception class has some possibly useful methods you can try out to get additional information about the error/crash and Exception thrown, see below table:

.getMessage() Gets the detailed message of what happened
.toString() Returns a short description text including concatenation of name of the class thrown, and the result of invoking .getLocalizedMessage() method for the Exception
.printStackTrace() Prints what was thrown and its backtrace

Throw your own Exceptions in Java

You can also throw Exceptions yourself instead of letting other peoples code you’re re-using throw them for you (Any and every class you use that throws Exceptions – some programmer has specified in specific methods to throw that specific Exception at a specific point – you can do the same in your program).

You do this by using the throw-clause, like so:

throw Exception;
// or:
throw NullPointerException;

So what happens when you throw an Exception in your program yourself, is that you can also catch it “up the river” meaning for example if you were to throw a specific Exception inside of one of your methods in a class you are coding, your method first of all needs to have a “warning” for those that is going to use it, informing that this particular method of yours throws a particular type of Exception – once thats done – you can then “catch” possible errors that method might throw – in your program where you call on this method – and deal with the Exception that was thrown “up the river” a.k.a. where the method was used/called, instead of in the method itself!

This way you could create some pretty sophisticated error handling for your programs.

Try-catch demonstrated in Java

Try-Catch statements are very simple in Java, and can help you prevent fatal errors that crash your program by catching possible errors and handling them a better way by for example informing your user what happened and guiding them to the next user-friendly step in the process, instead of leaving them confused and frustrated, not knowing what has happened or why.

The basic principle of a Try-Catch statement is that you wrap the code you wish to “try” inside of a Try-clause, like so:

Try {
   // I want to try this code...
}

Then when your compiler tries to execute the code inside of your Try-clause, it might give off error which might be in the form of an “Exception” – and Exceptions can be “caught” with a catch-clause, like so:

Try {
   // I want to try this code...

} catch(Exception e) {
   // Code for what to do when the exception was caught goes here...
}

Note that in above code example we catch the Exception with the help of the Java Exception class that we parse as input-parameter e into our catch-clause to be able to use it if we want to when dealing with the particular exception thrown.

For now we will just stick with the parent class for Exception in Java, it will do just fine for our intents and purposes here.

It is good praxis- and sometimes necessary in Java to wrap certain functionality in a Try-catch statement to make sure that the program works optimally towards the user in any given circumstances and situations.

Try-catch is very commonly used for closeable resources such as data streams.

A Try-catch statement can have multiple Exception handler catch-clauses to deal with various possible errors that might occur as well, see below example of such a statement:

try {
   // I want to try this code...
} catch(Exception e1) {
   // Deal with e1 Exception if that was thrown here

} catch(NullPointerException e2) {
   // Deal with e2 NullPointerException if that was thrown here
}

One thing to note however using multiple catch-clauses for Try-catch – is that the first Exception handler it can match to the Exception thrown from the error that occurred, it will trigger – so if you are to put Exception – which is parent of ALL Exceptions, you might want to place it at the very bottom, not to trigger before any of your more specific Exception handlers catch-clauses have had the chance of being triggered. Java will use “first best” catch-clause that matches the Exception thrown.

For our Try-catch statement we are however not limited to only catch-clauses, we can also add a finally-clause which is considered good praxis even when no exceptions are expected to put cleanup code in since the finally block always executes when a try-block exist – even if an Exception is thrown and error occurs. See example of the finally-clause below:

try {
   // I want to try this code...
} catch(Exception e) {
   // Deal with Exception thrown...
} finally {
   // Put "cleanup code" here that will be executed no matter if try-block crashes and Exception is thrown, or not
}

For more information about Exceptions and Try-catch statement in Java, check out Oracle Java Documentation.

Working with the URI class in Java

The URI class can be used to create a URI that then later can be used together with the Path class to specify a Path that then can be used with File class to open the specific File.

See below two examples of how to create a URI object for a specific “path”:

// Creating a URI object the "classical" way
URI uri = new URI("C:\\testfolder\\logfile");

Working with the Path & Paths classes in Java

The Path class can be used to store the path to be used for when opening files in Java. It will keep track of both the filename as well as the directories necessary to construct the full path to the file.

The Path class is platform dependent and may reflect the system root for whichever system the program is run on (Windows/Linux etc. where directory structure differ).

Via the File class later you can check to verify the existence of supposed Path stored within specified Path object.

We can help create a Path object with the Paths class which contains static methods to return a path by converting a String or URI.

See examples of creating a Path object both with and without the URI’s class below:

Path path = Paths.get("C:\\testfolder\\logfile"); // Converts a String to a Path object - notice double-backslashes to "escape" the actual backslash since it's inside of a String
Path URIPath = Paths.get(URI.create("C:\\testfolder\\logfile"));

To clarify a bit more in detail about the Paths.get() method – it is a shortcut doing the same as below:

Path path = FileSystems.getDefault().getPath("C:\\testfolder\\logfile");

As you can see above we use the FileSystems class in Java with one of its static methods (.getDefault()) to fetch the FileSystem object containing working directory/current user directory and can then call .getPath() method which is part of the FileSystem class to retrieve a usable Path object.

Also worth noting is that there is a chance that path-conversion with Paths.get() can sometimes turn the path into what you specified, with an added file:// in front, this is file URI scheme for files on local computer, so you don’t get surprised if you suddenly see URI looking like: file:///testfolder/logfile if you wrote .getPath("/testfolder/logfile"); You can read more about this here on Stackoverflow.

Working with the File & Files classes in Java

Creating a File object in Java to work with

There are two easy ways to create/specify a file in Java using the File class, one is by creating a File object with the pathname to the File you want to work with as a String originating from your systems Root folder (for windows its C:\), the other is to use the URI class to specify the path to the file. See below two examples:

// Creating a File object with a regular String for pathname
File file = new File("C:\\testfolder\\logfile");

// Creating a File object with URI static method .create() instead of String directly
File file2 = new File(URI.create("C:\\testfolder\\logfile"));

Check and Verify File Existence in Java with Files class

The Files class filled with useful static methods have among others two methods to verify whether a File exist or not, see below examples:

boolean fileExist = Files.exists(Paths.get("C:\\testfolder\\logfile")); // Using Paths static method .get() to convert String to Path object which .exists() method of Files require

Note: The Files.exists(Path path) method can return 3 values, true if the file exists, false if it doesn’t, or unknown if for example the system cannot access the file!

Check and Verify File Accessibility in Java with Files class

The Files class in Java also got two really useful static methods to check the accessibility (if file is readable and writeable) of a file specified for a specific Path, they look as follows:

boolean readable = Files.isReadable(Paths.get("C:\\testfolder\\logfile")); // Returns true if exist and is readable, false if the file does not exist
boolean writeable = Files.isWritable(Paths.get("C:\\testfolder\\logfile")); // Returns true if exist and is writable, false if the file does not exist

Reading from and writing to Files in Java by working with different types of Data Streams

Working with Byte Streams in Java

When using Byte Streams we work with a file 1 Byte at a time. Because of this, it is a sort of low-level I/O and should therefore be avoided most times. If we are to work with for example text data, we would be much better off working with the Character Stream.

Byte Stream should only be used for the most simple Input/Output operations.

Opening and reading from a file with Byte Streams in Java + making it Buffered

// Imports necessary to open and work with a file using our Byte Stream
import java.io.FileInputStream;
import java.io.IOException;

// Creating our Input Stream object - by declaring it as null outside of try-catch we can use even after try-catch statement while still get the benefit or error handling try-catch provides
FileInputStream inputStream = null; // Oracle Java Documentation calls this in their example "in" which you can relate to System.in used for Scanner to tell where to read from

try {
   inputStream = new FileInputStream("C:\\testfolder\\logfile"); // Do Note that we here can replace the String path with a File object!

   // And to make the above inputStream buffered, instead we would write like this below:
   // inputStream = new BufferedInputStream(new FileInputStream("C:\\testfolder\\logfile"));

   int readByteValue = inputStream.read(); // This will read the first Byte of data from our inputStream and store it as an integer value since that is what .read() method returns

   // If we were to wish to read the entire file one Byte at a time, we can use the while-loop to read for as long as we can read, like so:
   while((readByteValue = inputStream.read()) != -1) { // FileInputStream's .read() method will return -1 when EOF (end of file) marker has been reached
      // Process the Byte data read here
   }

} catch(IOException e) {

   // Deal with potential IOException that may be thrown if something goes wrong with the I/O operations
   System.out.println("IOException thrown, error message reads: " + e.getMessage());

} finally {
   // Here we "clean up" by closing any still open Data Streams (which in this case is our inputStream that we created) - regardless of error/Exception thrown or not
   if(inputStream != null) {
      inputStream.close(); // Closing the inputStream and freeing up its allocated system resources to improve performance after finished using it
   }
}

Above example was inspired from Oracle Java Documentation with added comments and alterations to make it easier to understand each part and how it all works together.

Writing to a file with Byte Streams in Java

// Imports necessary to open and work with a file using our Byte Stream
import java.io.FileOutputStream;
import java.io.IOException;

// Creating our Output Stream object - by declaring it as null outside of try-catch we can use even after try-catch statement while still get the benefit or error handling try-catch provides
FileOutputStream outputStream = null; // Oracle Java Documentation calls this for example "out" which you can relate to System.out for formatting and writing to the console

try {
   outputStream = new FileOutputStream("C:\\testfolder\\outputFile"); // Do Note that we here can replace the String path with a File object!

   // And to make the above outputStream buffered, instead we would write like this below:
   // outputStream = new BufferedOutputStream(new FileOutputStream("C:\\testfolder\\outputFile"));

   int valueToWrite = 15; // FileOutputStream's .write() method can write either Byte array data or an integer with a Byte value, in this case we demo writing an integer Byte value

   outputStream.write(valueToWrite);

} catch(IOException e) {

   // Deal with potential IOException that may be thrown if something goes wrong with the I/O operations
   System.out.println("IOException thrown, error message reads: " + e.getMessage());

} finally {
   // Here we "clean up" by closing any still open Data Streams (which in this case is our outputStream that we created) - regardless of error/Exception thrown or not
   if(outputStream != null) {
      outputStream.close(); // Closing the outputStream, freeing up allocated system resources to improve performance after done using it
   }
}

Above example was inspired from Oracle Java Documentation with added comments and alterations to make it easier to understand each part and how it all works together.

Working with Character Streams in Java

The Character Stream is great when working with text data. We will in this section demonstrate to you how you can both open and read from files using Character Streams, as well as write to them.

Opening and reading from a file with Character Streams in Java

// Imports necessary to open and work with a file using our Character Stream
import java.io.FileReader;
import java.io.IOException;

// Creating our Input Stream object - by declaring it as null outside of try-catch we can use even after try-catch statement while still get the benefit or error handling try-catch provides
FileReader inputStream = null; // Oracle Java Documentation calls this in their example "in" which you can relate to System.in used for Scanner to tell where to read from

try {
   inputStream = new FileReader("C:\\testfolder\\logfile"); // Do Note that we here can replace the String path with a File object!

   // And to make the above inputStream buffered, instead we would write like this below:
   // inputStream = new BufferedReader(new FileReader("C:\\testfolder\\logfile"));

   int readCharacterValue = inputStream.read(); // This will read the first character of data from our inputStream and store it as an integer value since that is what .read() method of the InputStreamReader class returns

   // If we were to wish to read the entire file one character at a time, we can use the while-loop to read for as long as we can read, like so:
   while((readCharacterValue = inputStream.read()) != -1) { // FileInputStream's (which is extended from FileReader) .read() method will return -1 when EOF (end of file) marker has been reached
      // Process the Character data read here
   }
} catch(IOException e) {

   // Deal with potential IOException that may be thrown if something goes wrong with the I/O operations
   System.out.println("IOException thrown, error message reads: " + e.getMessage());

} finally {
   // Here we "clean up" by closing any still open Data Streams (which in this case is our inputStream that we created) - regardless of error/Exception thrown or not
   if(inputStream != null) {
      inputStream.close(); // Closing the inputStream and freeing up its allocated system resources to improve performance after finished using it
   }
}

Above example was inspired from Oracle Java Documentation with added comments and alterations to make it easier to understand each part and how it all works together.

Writing to a file with Character Streams in Java

// Imports necessary to open and work with a file using our Character Stream
import java.io.FileWriter;
import java.io.IOException;

// Creating our Output Stream object - by declaring it as null outside of try-catch we can use even after try-catch statement while still get the benefit or error handling try-catch provides
FileWriter outputStream = null; // Oracle Java Documentation calls this for example "out" which you can relate to System.out for formatting and writing to the console

try {
   outputStream = new FileWriter("C:\\testfolder\\outputFile"); // Do Note that we here can replace the String path with a File object!

   // And to make the above outputStream buffered, instead we would write like this below:
   // outputStream = new BufferedWriter(new FileWriter("C:\\testfolder\\outputFile"));

   int characterAInASCII8bit = 97; // FileOutputStream's .write() method can write either Byte array data or an integer with a Byte value, in this case we demo writing an integer ASCII 8-bit character value

   outputStream.write(characterAInASCII8bit);

} catch(IOException e) {

   // Deal with potential IOException that may be thrown if something goes wrong with the I/O operations
   System.out.println("IOException thrown, error message reads: " + e.getMessage());

} finally {
   // Here we "clean up" by closing any still open Data Streams (which in this case is our outputStream that we created) - regardless of error/Exception thrown or not
   if(outputStream != null) {
      outputStream.close(); // Closing the outputStream, freeing up allocated system resources to improve performance after done using it
   }
}

Above example was inspired from Oracle Java Documentation with added comments and alterations to make it easier to understand each part and how it all works together.

Working with Data Streams in Java

Data Streams in this instance particularly refers to the Data Stream specialized in primitive data type values as well as String values.

Data Streams uses the DataOutputStream and DataInputStream classes in Java which can only be created as a wrapper for an existing Byte Stream.

Opening and reading from a file with Data Streams in Java

// Imports necessary to open and work with a file using our DataInput wrapped Byte Stream
import java.io.DataInputStream;
import java.io.EOFException;

// Creating our Input Stream object - by declaring it as null outside of try-catch we can use even after try-catch statement while still get the benefit or error handling try-catch provides
FileInputStream inputStream = null; // Oracle Java Documentation calls this in their example "in" which you can relate to System.in used for Scanner to tell where to read from

try {
   inputStream = new DataInputStream(new BufferedInputStream(new FileInputStream("C:\\testfolder\\logfile"))); // Here we create our DataInputStream object by first converting our FileInputStream Byte Stream to a BufferedInputStream and then wrapping it as a DataInputStream, also Do Note that we here can replace the String path with a File object!

   int readIntegerValue = inputStream.readInt(); // This will read an integer record from our inputStream and store it as an integer value
   String readStringValue = inputStream.readUTF(); // This will read a String value in Unicode character encoding as a modified UTF-8 format from our inputStream, you can read more about Unicode here
   
   // If we were to wish to read the entire file one record at a time, we can use the while-loop to read for as long as we can read, like so:
   while(true) { // DataInputStreams detects EOFException instead of a return value from the read operations for when to stop reading - hence the while(true) infinite-loop-looking statement
      // Read data and process it from the inputStream here...
   }

} catch(IOException e) {

   // The reading of the entire file will stop once it detects EOFException, catch it and deal with it here...

} finally {
   // Here we "clean up" by closing any still open Data Streams (which in this case is our inputStream that we created) - regardless of error/Exception thrown or not
   if(inputStream != null) {
      inputStream.close(); // Closing the inputStream and freeing up its allocated system resources to improve performance after finished using it
   }
}

Above example was inspired from Oracle Java Documentation with added comments and alterations to make it easier to understand each part and how it all works together.

Writing to a file with Data Streams in Java

// Imports necessary to open and work with a file using our DataOutput wrapped Byte Stream
import java.io.DataOutputStream;
import java.io.IOException;

// Creating our Output Stream object - by declaring it as null outside of try-catch we can use even after try-catch statement while still get the benefit or error handling try-catch provides
FileOutputStream outputStream = null; // Oracle Java Documentation calls this for example "out" which you can relate to System.out for formatting and writing to the console

try {
   outputStream = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("C:\\testfolder\\outputFile"))); // Here we create our DataOutputStream object by first converting our FileOutputStream Byte Stream to a BufferedOutputStream and then wrapping it as a DataOutputStream, also Do Note that we here can replace the String path with a File object!

   outputStream.writeInt(); // This will write an integer record to our outputStream
   outputStream.writeUTF(); // This will write a String value in Unicode character encoding as a modified UTF-8 format to our outputStream, you can read more about Unicode here

} catch(EOFException e) {

   // Deal with potential IOException that may be thrown if something goes wrong with the I/O operations
   System.out.println("IOException thrown, error message reads: " + e.getMessage());

} finally {
   // Here we "clean up" by closing any still open Data Streams (which in this case is our outputStream that we created) - regardless of error/Exception thrown or not
   if(outputStream != null) {
      outputStream.close(); // Closing the outputStream, freeing up allocated system resources to improve performance after done using it
   }
}

Above example was inspired from Oracle Java Documentation with added comments and alterations to make it easier to understand each part and how it all works together.

Working with Object Streams in Java

Object Streams allow us to read object data from files as well as write object data to files. This can be very useful for persistent storage of your programdata when program is about to shut down, to save the “state” until the program starts up anew next time, to not lose out on data and have to do things all over again.

Object Streams use interfaces based on the same interface used for Data Streams, which means that Object Streams can both read/write object data as well as primitive data.

Opening and reading from a file with Object Streams in Java

// Imports necessary to open and work with a file using our ObjectInput wrapped Byte Stream
import java.io.DataInputStream;
import java.io.ClassNotFoundException;

// Creating our Input Stream object - by declaring it as null outside of try-catch we can use even after try-catch statement while still get the benefit or error handling try-catch provides
ObjectInputStream inputStream = null; // Oracle Java Documentation calls this in their example "in" which you can relate to System.in used for Scanner to tell where to read from

try {
   inputStream = new ObjectInputStream(new FileInputStream("C:\\testfolder\\logfile")); // Here we create our ObjectInputStream object by wrapping a FileInputStream, also Do Note that we here can replace the String path with a File object!

   Object obj = (Object)inputStream.readObject(); // Here we assume a file holds only 1 object and the type-casting can be used to convert the read object to the proper Class type

} catch(Exception e) {

   // Deal with possible Exception that might get thrown

} finally {
   // Here we "clean up" by closing any still open Data Streams (which in this case is our inputStream that we created) - regardless of error/Exception thrown or not
   if(inputStream != null) {
      inputStream.close(); // Closing the inputStream and freeing up its allocated system resources to improve performance after finished using it
   }
}

Above example was inspired from Oracle Java Documentation with added comments and alterations to make it easier to understand each part and how it all works together.

Writing to a file with Object Streams in Java

// Imports necessary to open and work with a file using our DataOutput wrapped Byte Stream 
import java.io.DataOutputStream; 
import java.io.IOException; 

// Creating our Output Stream object - by declaring it as null outside of try-catch we can use even after try-catch statement while still get the benefit or error handling try-catch provides 
FileOutputStream outputStream = null; // Oracle Java Documentation calls this for example "out" which you can relate to System.out for formatting and writing to the console 

try {
   outputStream = new ObjectOutputStream(new FileOutputStream("C:\\testfolder\\outputFile")); // Here we create our ObjectOutputStream object by wrapping it a FileOutputStream, also Do Note that we here can replace the String path with a File object! 
   
   Object obj = new Object(); // Assume we have an object we wish to write

   outputStream.writeObject(obj); //Then we write it to our outputStream like so

} catch(EOFException e) {

   // Deal with possible Exception that might get thrown

} finally { 
   // Here we "clean up" by closing any still open Data Streams (which in this case is our outputStream that we created) - regardless of error/Exception thrown or not 
   if(outputStream != null) { 
      outputStream.close(); // Closing the outputStream, freeing up allocated system resources to improve performance after done using it 
   } 
}

Above example was inspired from Oracle Java Documentation with added comments and alterations to make it easier to understand each part and how it all works together.


There is a great collection of articles covering all possibilities for working with Files in the Oracle Java Documentation if you’re interested.

Leave a Reply

Your email address will not be published. Required fields are marked *