1. Introduction: serializing objects with reference fields
In real-world applications, you rarely see completely “flat” classes. Typically, an object contains other objects, which in turn may contain something else. This is called composition (or nesting) of objects. For example:
public class Address {
String city;
String street;
}
public class Person {
String name;
int age;
Address address; // Nested object!
}
When we serialize such an object, a question arises: what should we do with the address field? Should Java serialize it together with Person? And what if Address has yet another object inside? Fortunately (or unfortunately—depends on the situation), Java by default serializes all nested objects recursively, if they also implement the Serializable interface.
Serialization in Java is always deep serialization. This means not only the object itself is serialized, but also all objects it references through its (non-transient) fields, and so on—all the way to the very “bottom”.
Process visualization
graph TD
A[Person] --> B[Address]
B --> C[CityInfo]
A --> D[Pet]
In short, if you decided to serialize Person, then Java will serialize Address as well, and everything inside Address, and so on.
2. Requirements for nested objects: Serializable is mandatory!
You’ve probably already noticed an important nuance: all nested objects that are serialized must also implement the Serializable interface.
If at least one reference field points to an object that does not implement Serializable, the serialization attempt will end with java.io.NotSerializableException.
Let’s look at examples of deep serialization.
Example: everything is fine
import java.io.Serializable;
public class Address implements Serializable {
String city;
String street;
}
public class Person implements Serializable {
String name;
int age;
Address address;
}
Both classes implement Serializable. Everything works; serialization succeeds.
Example: error!
public class Address { // does NOT implement Serializable!
String city;
String street;
}
public class Person implements Serializable {
String name;
int age;
Address address;
}
As we can see, here Address does not implement the Serializable interface. So when serializing Person, we will get a NotSerializableException.
3. Example: serialization and deserialization with a nested object
Let’s see how this looks in practice. In the previous level, we worked with the “Contacts Manager” application. Now let’s add an address for each user.
import java.io.*;
class Address implements Serializable {
String city;
String street;
Address(String city, String street) {
this.city = city;
this.street = street;
}
}
class Person implements Serializable {
String name;
int age;
Address address;
Person(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}
}
public class SerializationDemo {
public static void main(String[] args) throws Exception {
Person p = new Person("John", 30, new Address("Prague", "Slavinska, 1"));
// Serialization
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("person.ser"));
out.writeObject(p);
out.close();
// Deserialization
ObjectInputStream in = new ObjectInputStream(new FileInputStream("person.ser"));
Person restored = (Person) in.readObject();
in.close();
System.out.println(restored.name + ", " + restored.age + ", " +
restored.address.city + ", " + restored.address.street);
}
}
Result:
John, 30, Prague, Slavinska, 1
Everything works: the nested Address object is serialized and restored together with Person.
4. What if a nested object is not serializable?
If you try to serialize an object that has at least one reference field pointing to an object that does not implement Serializable, Java will throw an exception at the moment of the attempt.
Error demonstration
class Address { // not Serializable!
String city;
String street;
}
class Person implements Serializable {
String name;
Address address;
}
public class Test {
public static void main(String[] args) throws Exception {
Person p = new Person();
p.name = "Peter";
p.address = new Address();
p.address.city = "Derry";
p.address.street = "Elm";
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("person.ser"));
out.writeObject(p); // <-- AN EXCEPTION WILL BE THROWN!
out.close();
}
}
Error:
java.io.NotSerializableException: Address
5. Transient for nested objects
What should you do if you have a reference field to an object that must not be serialized (for example, a cache, a database connection, a temporary object)? In that case, declare the field as transient. Then Java will simply skip this field during serialization.
Example with transient
class Address { // not Serializable
String city;
String street;
}
class Person implements Serializable {
String name;
transient Address address; // transient!
Person(String name, Address address) {
this.name = name;
this.address = address;
}
}
public class Test {
public static void main(String[] args) throws Exception {
Address addr = new Address();
addr.city = "Los Santos";
addr.street = "Mulholland Drive";
Person p = new Person("Alex", addr);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("person.ser"));
out.writeObject(p);
out.close();
// Deserialization
ObjectInputStream in = new ObjectInputStream(new FileInputStream("person.ser"));
Person restored = (Person) in.readObject();
in.close();
System.out.println(restored.name); // "Alex"
System.out.println(restored.address); // null!
}
}
Result:
The address field after deserialization is null because it is transient.
6. Deep nesting and recursion
Serialization works recursively: if Person has an Address field, and Address has a CityInfo field, and so on, the serializer will keep “diving” deeper until it meets something non-serializable or until memory runs out (a joke—but only half).
Important: cyclic references
The Java serializer can handle cyclic references. If, for example, an object has a reference to another object, which in turn refers back, the serializer won’t get stuck in a loop; it will carefully preserve the structure.
class A implements Serializable {
B b;
}
class B implements Serializable {
A a;
}
If you create A and B objects that reference each other, serialization will not cause a StackOverflowError—Java remembers objects that have already been serialized.
7. Example: serializing an object with a nested list
Often an object contains collections of other objects. For example, a user may have a list of friends:
import java.io.*;
import java.util.*;
class Person implements Serializable {
String name;
List<Person> friends;
Person(String name) {
this.name = name;
this.friends = new ArrayList<>();
}
}
public class FriendsSerialization {
public static void main(String[] args) throws Exception {
Person alice = new Person("Alice");
Person bob = new Person("Bob");
alice.friends.add(bob);
// Serialization
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("friends.ser"));
out.writeObject(alice);
out.close();
// Deserialization
ObjectInputStream in = new ObjectInputStream(new FileInputStream("friends.ser"));
Person restored = (Person) in.readObject();
in.close();
System.out.println(restored.name); // Alice
System.out.println(restored.friends.get(0).name); // Bob
}
}
Important: Java standard library collections (ArrayList, HashMap, etc.) implement Serializable, so everything works “out of the box”.
8. Common mistakes when serializing nested objects
Error No. 1: One of the nested objects does not implement Serializable. You forgot to add implements Serializable to one of the nested classes. The result is a NotSerializableException on the first serialization attempt. Check that all classes in your serialization chain support this interface.
Error No. 2: A non-serializable field is not declared transient. If you have a field that should not be serialized (for example, a stream, a database connection, something temporary), but you didn’t declare it as transient, serialization will fail with an error. Don’t forget about transient!
Error No. 3: serialVersionUID mismatch in nested classes. If you explicitly declare serialVersionUID in nested classes and change their structure, don’t forget to update this identifier—otherwise, errors during deserialization are possible.
Error No. 4: Mutating (mutable) nested objects. If you serialize a collection or an object that then changes (for example, a friends list), after deserialization it will be a “snapshot” at the time of serialization. New changes in the original will not be reflected in the deserialized object.
Error No. 5: Serializing huge object graphs. If you have a very complex structure with many nested objects, serialization can take a lot of time and memory. Sometimes it’s better to serialize only the key data, not the entire structure.
GO TO FULL VERSION