Learning outcome: Save and restore object state using object streams, explain Serializable, transient, object-graph traversal, and version compatibility, and recognise common serialization failures and trust-boundary risks.
A student object contains related values, such as a roll number, name, and marks. Serialization converts object state into a byte stream that Java can later read. Deserialization reconstructs objects from that stream. Unlike a human-readable report, a Java object stream also carries information needed to rebuild the object graph, including class descriptors and reference relationships.
Serializable and Object Streams
java.io.Serializable is a marker interface: it declares no methods. A class implements it to participate in Java's standard serialization mechanism. ObjectOutputStream.writeObject writes an object graph, while ObjectInputStream.readObject reads an object and returns it as Object, so application code normally casts it to the expected type. See Oracle's object stream overview.
| Element | Purpose |
|---|---|
Serializable |
Marks a class as eligible for serialization |
ObjectOutputStream |
Writes primitive values and object graphs |
ObjectInputStream |
Reads primitive values and object graphs |
serialVersionUID |
Declares a class version identifier checked during deserialization |
transient |
Excludes an instance field from default serialization |
With default serialization, non-static, non-transient instance fields are stored. Primitive fields are saved directly as part of the object state; they do not need to be converted to wrapper objects first. When a field refers to another object, serialization follows that reference, so every reachable object that is not excluded must also be serializable. Otherwise, writing can fail with NotSerializableException.
null here).Save and Restore a Student
Save as SerializationDemo.java. The program creates or replaces student.ser in the working directory and reads that same trusted file back.
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class SerializationDemo {
static class Student implements Serializable {
private static final long serialVersionUID = 1L;
int rollNumber;
String name;
int marks;
transient String sessionNote;
Student(int rollNumber, String name, int marks, String note) {
this.rollNumber = rollNumber;
this.name = name;
this.marks = marks;
this.sessionNote = note;
}
}
public static void main(String[] args) {
Student original = new Student(101, "Asha", 84, "Editing now");
try {
try (FileOutputStream file = new FileOutputStream("student.ser");
ObjectOutputStream out = new ObjectOutputStream(file)) {
out.writeObject(original);
}
try (FileInputStream file = new FileInputStream("student.ser");
ObjectInputStream in = new ObjectInputStream(file)) {
Student restored = (Student) in.readObject();
System.out.println(restored.rollNumber + " "
+ restored.name + " " + restored.marks);
System.out.println("Session note: " + restored.sessionNote);
System.out.println("Same object: " + (original == restored));
}
} catch (IOException | ClassNotFoundException e) {
System.err.println("Save or restore failed: " + e.getMessage());
}
}
}
Output:
101 Asha 84
Session note: null
Same object: false
The roll number, name, and marks survive the round trip. The transient reference is absent from the default serialized state and therefore has its default value, null, after restoration. A transient int would be 0 and a transient boolean would be false. The restored student is a newly allocated object, so original == restored is false.
What Is and Is Not Preserved?
Serialization preserves sharing and cycles within an object graph. If two saved objects refer to the same serializable Address instance, default deserialization restores one corresponding address object and reconnects both references to it. In other words, graph relationships are preserved even though the original JVM memory addresses are not.
Static fields belong to the class rather than an individual object, so default serialization does not save them as instance state. Methods and executable class definitions are not copied into the serialized file. The receiving program needs compatible classes. For an ordinary Serializable class, deserialization does not run that class's constructor; it invokes the accessible no-argument constructor of the first non-serializable superclass. See the ObjectInputStream API.
In simple terms: Serialization saves the state needed to restore an object graph. It does not package the entire running program, its methods, or its open connections.
Versioning and Failures
Declare private static final long serialVersionUID = 1L; deliberately for classes whose serialized form may outlive one program run. If it is omitted, Java computes a default identifier from class details, which can change after seemingly small class modifications. A mismatch between the stream identifier and the local class identifier can cause InvalidClassException. Keeping the identifier unchanged does not make every class change compatible; the field structure and serialization rules still matter.
| Failure | Typical cause |
|---|---|
NotSerializableException |
A required object in the graph is not serializable |
ClassNotFoundException |
A class needed while restoring is unavailable |
InvalidClassException |
Class version or other compatibility problem |
EOFException |
Stream ends before a requested item can be read |
StreamCorruptedException |
Invalid object stream header or inconsistent stream data |
Do not repeatedly append new ObjectOutputStream instances to the same file as if writing plain text: each writes a stream header. For a simple record collection, write a serializable list in one session and read it back as one object.
Choosing a Persistence Format
This exercise demonstrates Java object persistence. For long-lived storage or data exchange between independently developed applications, formats such as JSON, a schema-based format, or a database often provide a clearer external contract. Java deserialization may invoke class-defined deserialization hooks while rebuilding objects, so never treat arbitrary uploaded or network-provided object streams as ordinary data. Production code should establish an explicit trust policy and use ObjectInputFilter or equivalent filtering and resource limits where object deserialization is unavoidable. See the ObjectInputStream API.
transient controls only default field persistence; it is not encryption or access control. A .ser file should therefore never be treated as a protected container for confidential data.
Practice
- Add a serializable
ArrayList<Integer>of marks to the student and round-trip it. - Add a transient integer attempt counter and predict its restored value.
- Add a non-serializable helper object, observe the failure, and decide whether it should be excluded or redesigned.
- Complete the related Unit 2 laboratory exercise on wrappers and file handling.
Quick Check
1. How many methods must a class implement for Serializable?
None. It is a marker interface.
2. Are static fields saved as part of default instance state?
No. They belong to the class and retain the values provided by the receiving application's class state.
3. Does transient make a field confidential?
No. It excludes the field from default serialization. It does not encrypt other data or prevent custom code from writing that value elsewhere.
Summary
Serialization can persist an object graph when the participating classes and stream data are compatible. Understand which fields and reachable objects become part of that graph, define versioning deliberately, exclude temporary state intentionally, and treat deserialization as a trust-boundary operation. Continue with object cloning and copying.