Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 20

Object Serialization with ObjectInputStream and ObjectOutputStream

Recommended resources

Hello! In our previous lesson, we worked with text-based data, learning to read from and write to files using BufferedReader and BufferedWriter. That's a vital skill for handling logs, configuration files, and simple data formats. Today, we'll take a significant step forward and learn how to persist the entire state of a Java object.

This lesson covers object serialization and deserialization. Serialization is the process of converting a live Java object into a stream of bytes, which can then be saved to a file or sent across a network. Deserialization is the reverse: converting that byte stream back into a fully functional object in memory. For backend development, this is a cornerstone technique used for caching, saving application state, and in various communication protocols between distributed services.

We will explore the ObjectOutputStream and ObjectInputStream classes, the primary tools for this task, and learn the rules and best practices that ensure your objects can be saved and restored reliably.

1. What is Object Serialization?

While text files are great for human-readable data, they can't capture the complete picture of a complex object. An object in Java isn't just its data; it's a structured entity in memory with a specific type, fields, and relationships. Serialization allows us to preserve this entire structure.

  • Serialization: The process of converting an object's state into a byte stream. This is done with ObjectOutputStream.
  • Deserialization: The reverse process of reconstructing an object from a byte stream. This is done with ObjectInputStream.

The following video from Bro Code provides an excellent high-level introduction to these concepts.

Java serialization is easy! 🥣

This video introduces the core ideas behind serialization and deserialization.

Watch the first part of the video (the explanation) to understand what serialization is and why it's useful for making an object's state persist after a program terminates.

The key classes for these operations, ObjectOutputStream and ObjectInputStream, act as decorators for other streams, much like the buffered readers and writers we saw previously. They wrap byte-oriented streams (like FileOutputStream and FileInputStream) to add the capability of handling objects.

This diagram shows the relationship between the base stream classes and the object stream classes. `ObjectOutputStream` uses the `writeObject()` method to serialize an object, while `ObjectInputStream` uses `readObject()` to deserialize it.

The process of deserialization can be visualized as reading raw bytes from a source and reconstructing them into a live object that your Java application can use.

This diagram illustrates how an `ObjectInputStream` reads a byte stream from a source (like a file), deserializes it, and returns a reconstructed `Object` to the Java application.

2. Serialization: Writing an Object to a File

To make an object serializable, there's one simple but mandatory rule: its class must implement the java.io.Serializable interface.

public class User implements Serializable { ... }

Serializable is a marker interface, meaning it has no methods to implement. It simply signals to the Java Virtual Machine (JVM) that you are giving it permission to serialize and deserialize instances of this class.

Once your class is serializable, the process of writing an object to a file involves three main steps, which should feel familiar:

  1. Create a FileOutputStream to specify the destination file (e.g., "user.ser"). The .ser extension is a common convention for serialized object files.
  2. Wrap the FileOutputStream in an ObjectOutputStream.
  3. Call the writeObject(yourObject) method on the ObjectOutputStream.

As always, we use a try-with-resources block to ensure the streams are closed properly.

The following article from Coding Shuttle provides a clear, complete example of this process.

ObjectInputStream and ObjectOutputStream | Coding Shuttle

This resource demonstrates how to serialize a Student object.

Read the section "Example". Note the Student class implementing Serializable and how ObjectOutputStream is used within the try-with-resources block to write the student object to a file named student.ser.


3. Deserialization: Reading an Object from a File

Deserialization brings our object back to life from the byte stream stored in the file. The process mirrors serialization:

  1. Create a FileInputStream that points to the .ser file.
  2. Wrap it in an ObjectInputStream.
  3. Call the readObject() method on the ObjectInputStream.

There are two critical details to handle during deserialization:

  • Casting: The readObject() method returns a value of type Object. You must cast this back to the specific class you are expecting (e.g., Student student = (Student) ois.readObject();).
  • Exception Handling: Besides the potential IOException, readObject() can also throw a ClassNotFoundException. This happens if the JVM cannot find the class definition for the object you are trying to deserialize (e.g., if the class file is missing from your project's classpath). Your catch block must handle both.

Let's return to the Coding Shuttle article to see the deserialization code.

ObjectInputStream and ObjectOutputStream | Coding Shuttle

This part of the article shows the reverse process: reading the Student object back from the file.

Now, study the example under the section "ObjectInputStream Methods". Pay close attention to two key parts: the cast (Student) ois.readObject() and the multi-catch block catch (IOException | ClassNotFoundException e).


4. Important Considerations for Robust Serialization

While the basics are straightforward, professional backend development requires attention to detail to avoid bugs and security vulnerabilities. Here are two essential concepts to master.

The transient Keyword

Sometimes, you have fields in an object that you do not want to serialize. A prime example is a user's password; for security reasons, you should never write it to a file. Other examples include fields that can be recalculated, or fields that hold references to non-serializable resources.

To exclude a field from serialization, you mark it with the transient keyword.

private transient String password;

When an object with a transient field is deserialized, that field will be initialized to its default value (null for objects, 0 for numbers, false for booleans).

The "Bro Code" video explains this concept very clearly.

Java serialization is easy! 🥣

This segment demonstrates how to use the transient keyword to prevent a field from being saved.

Watch the section explaining the transient keyword (the demonstration). The video shows how a password field marked as transient becomes null after deserialization.

The serialVersionUID

What happens if you serialize an object, then update its class (e.g., add a new field), and then try to deserialize the old object? By default, Java will throw an InvalidClassException. This is because the JVM implicitly calculates a version ID for each serializable class. If the version ID of the class in your code doesn't match the version ID stored in the serialized file, deserialization fails.

To prevent this and take control of versioning, it is a strong best practice to explicitly declare a serialVersionUID in your class.

private static final long serialVersionUID = 1L;

This is a unique ID for your class version. If you make a compatible change (like adding a field), you can leave the serialVersionUID the same. If you make an incompatible change (like changing a field's type), you should increment the version number. Most IDEs will offer to generate or add this field for you.

The "Bro Code" video provides an excellent walkthrough of why this is so important.

Java serialization is easy! 🥣

This final segment explains the purpose and importance of serialVersionUID.

Watch this detailed explanation of the serialVersionUID (the explanation). It shows exactly what happens when the UIDs don't match and how to declare one yourself.

For a concise textual summary of these important rules, the article from "Learn Code with Durgesh" is a great reference.

Object Streams in Java (ObjectInputStream & ObjectOutputStream) Explained | Learn Code With Durgesh - Learn Code with Durgesh

This resource provides a quick reference for the key rules of serialization.

Read the section titled "Important Notes". It efficiently summarizes the requirements we've discussed: implementing Serializable, the behavior of static and transient fields, and the purpose of serialVersionUID.


Conclusion

You have now completed the I/O module by learning how to persist entire objects, a powerful capability that moves beyond simple text files. This is a fundamental skill for building applications that need to maintain state between sessions, cache data for performance, or communicate in a distributed environment.

Key Takeaways:

  • Serialization converts an object into a byte stream using ObjectOutputStream and its writeObject() method.
  • Deserialization reconstructs an object from a byte stream using ObjectInputStream and its readObject() method.
  • A class must implement the Serializable marker interface to be eligible for this process.
  • When deserializing, you must cast the result of readObject() to the correct type and handle a potential ClassNotFoundException.
  • Use the transient keyword to prevent sensitive or temporary fields from being serialized.
  • Always declare a serialVersionUID in your serializable classes to manage version compatibility and avoid InvalidClassException.

With this lesson, we conclude our module on Exception Handling and I/O Streams. You are now equipped to handle errors gracefully and manage data both as text and as objects.

In our next lesson, we will begin a new module: Java Collections and Generics. We'll start by exploring ArrayList and LinkedList, two fundamental data structures for managing ordered groups of objects in memory.