Serialization in Java: Object Streams, transient, serialVersionUID and Runnable Examples

Write and restore a complete Student object, trace every retained field, and learn how Java handles transient, static, shared and evolving state.

KnowledgeGate Team

Exam prep & CS education

Updated 12 Sep 20266 min read

Student.toString() stores text, not a reconstructable object. The first program writes Student{id=42, name='Mira', cgpa=8.6, address=Pune-411001, password=gate@123} to student.ser with ObjectOutputStream, then restores it with ObjectInputStream. Its output separates retained object-graph state from transient and static state.

Build the correct mental model for Java Serialization

Serialization converts the state of a Java object graph into a byte stream. Deserialization uses that stream to build a new object graph. Serializable is a marker contract checked when ObjectOutputStream.writeObject() reaches a class. The model object does not call a Serializable method.

The mechanism is separate from the destination. Bytes can go to a file, memory buffer or connection. The student.ser file is a saved artefact; Java does not guarantee that it is human-readable or has a fixed size.

Java serialization suits controlled Java-to-Java persistence or transfer. Use a database, JSON or another explicit format when data must be queried, exchanged across languages or evolve independently. The Coding & Skill Development Courses category connects this mechanism to related programming skills.

Write and read one complete object

Save this complete file as SerializationDemo.java. Both Student and its reachable Address implement the marker interface.

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

final class Address implements Serializable {
    private static final long serialVersionUID = 1L;
    private final String city;
    private final int pinCode;

    Address(String city, int pinCode) {
        this.city = city;
        this.pinCode = pinCode;
    }

    @Override
    public String toString() {
        return city + "-" + pinCode;
    }
}

final class Student implements Serializable {
    private static final long serialVersionUID = 1L;
    private final int id;
    private final String name;
    private final double cgpa;
    private final Address address;
    private final transient String password;

    Student(int id, String name, double cgpa, Address address, String password) {
        this.id = id;
        this.name = name;
        this.cgpa = cgpa;
        this.address = address;
        this.password = password;
    }

    @Override
    public String toString() {
        return "Student{id=" + id + ", name='" + name + "', cgpa=" + cgpa
                + ", address=" + address + ", password=" + password + "}";
    }
}

public class SerializationDemo {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Student original = new Student(42, "Mira", 8.6,
                new Address("Pune", 411001), "gate@123");
        File file = new File("student.ser");

        System.out.println("Before: " + original);
        try (ObjectOutputStream output =
                     new ObjectOutputStream(new FileOutputStream(file))) {
            output.writeObject(original);
        }
        System.out.println("Saved to: " + file.getName());

        Student restored;
        try (ObjectInputStream input =
                     new ObjectInputStream(new FileInputStream(file))) {
            restored = (Student) input.readObject();
        }
        System.out.println("After: " + restored);
        System.out.println("Same object: " + (original == restored));
    }
}

Compile and run it:

javac SerializationDemo.java
java SerializationDemo

The output is exactly:

Before: Student{id=42, name='Mira', cgpa=8.6, address=Pune-411001, password=gate@123}
Saved to: student.ser
After: Student{id=42, name='Mira', cgpa=8.6, address=Pune-411001, password=null}
Same object: false

Java restores 42, 8.6, "Mira", "Pune", 411001 and the reachable Address. It gives the excluded reference its default value, null. Deserialization creates a distinct Student, so identity comparison is false even though the retained values match.

A Student written to student.ser and read back: the transient password becomes null and the restored object differs from the original.

Follow the reachable object graph and its exclusions

The Student -> Address link shows the transitive rule. Every non-transient object reachable from the root must be serializable. Remove implements Serializable only from Address and compilation still succeeds, but writeObject(original) throws NotSerializableException naming Address. Either make Address serializable because it is retained state, or mark the field transient because exclusion is deliberate.

After reading, ordinary instance fields retain id=42, name="Mira", cgpa=8.6 and address=Pune-411001. The transient reference password becomes null. A hypothetical static String college = "KG" comes from the currently loaded class, not student.ser. A transient primitive such as int attempts = 3 becomes its default value, 0, unless custom restoration assigns something else.

Java preserves shared references within one stream. Compile the first file, then save this second program as SharedReferenceDemo.java:

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class SharedReferenceDemo {
    public static void main(String[] args) throws Exception {
        Address shared = new Address("Pune", 411001);
        Address[] original = {shared, shared};
        ByteArrayOutputStream bytes = new ByteArrayOutputStream();

        try (ObjectOutputStream output = new ObjectOutputStream(bytes)) {
            output.writeObject(original);
        }

        Address[] restored;
        try (ObjectInputStream input = new ObjectInputStream(
                new ByteArrayInputStream(bytes.toByteArray()))) {
            restored = (Address[]) input.readObject();
        }

        System.out.println(restored[0] == restored[1]);
    }
}

Compile and run it with javac SharedReferenceDemo.java && java SharedReferenceDemo. It prints true: both array slots point to one restored Address, because the stream records a back-reference. Binary Trees and Binary Search Trees offers a contrasting graph structure.

Control class evolution with serialVersionUID

Both classes declare private static final long serialVersionUID = 1L. The stream records class identity and version information. An explicit UID makes the compatibility decision visible instead of depending on a generated value that may change after class edits.

Try two runs. First, compile version 1 with UID 1L, create student.ser, and keep the file. Next, add private String branch; to Student, retain UID 1L, and use a read-only main on the old file. The old stream has no branch value, so restored.branch is null. The serializable Student constructor and field initializer do not run to invent a value during reading.

Now change only the Student UID to 2L and read the same file. Java throws InvalidClassException; its full message can vary by JVM. Keeping a UID unchanged does not make every structural change compatible, and it should never disguise two meaningfully different schemas as safe.

Reading old student.ser: serialVersionUID 1L restores it with branch=null, while UID 2L throws InvalidClassException.

Diagnose common failures and protect the boundary

Use the symptom to find the faulty assumption:

  • NotSerializableException: a reachable, non-transient object such as Address lacks the contract. Make it serializable or exclude it deliberately.

  • InvalidClassException: the stream version is incompatible with the loaded class. Handle the schema intentionally instead of changing the UID until the error disappears.

  • EOFException: the reader commonly called readObject() more times than the writer called writeObject(). Align the count and order.

  • StreamCorruptedException: the input is not a valid object stream, or extra stream headers were appended incorrectly. Recreate the file or use a format designed for appending.

Two outputs are not failures. password=null is the intended result of transient, and original == restored is false because Java built a new graph. Compare fields or implement a suitable equals() when you need value equality.

Never deserialize bytes from an untrusted user, attachment or network source merely because the cast compiles. Keep this example local and controlled. transient does not encrypt or erase a secret. It only excludes the field from the default serialized form, while student.ser still needs normal access controls.

Practise output and interview question shapes

Useful assessment prompts ask you to predict values after a write-read cycle, find the first non-serializable reachable field, separate instance, transient and static state, explain a UID mismatch, or reason about constructors.

KnowledgeGate currently has about 50 Java practice questions across its Java topics.

Try these before opening the answers:

  1. E1: Change the password field to transient int attempts = 3. What value follows a default read?

  2. E2: Add static String college = "KG", write the object, set Student.college = "AI" in the same JVM, then read. What does restored.college observe?

  3. E3: Point home and mailing at the same Address("Pune", 411001) in one object. Is restored.home == restored.mailing true or false?

  4. E4: Remove Serializable from Address. Where does failure occur?


  1. A1: 0, the default int value, because the field is transient.

  2. A2: "AI", because static state belongs to the loaded class and is not restored from the stream.

  3. A3: true, because the shared reference is preserved within the serialized graph.

  4. A4: Compilation succeeds, then writeObject() throws NotSerializableException for the reachable Address object.

Short version and the next Java step

Implement Serializable on each retained referenced-object class. Write with ObjectOutputStream, read in matching order with ObjectInputStream, exclude fields with transient, and manage evolution with an explicit serialVersionUID. Run both programs and predict their output. In the versioning experiment, a newly written object restores branch="CSE"; the old stream restores null.

For the full language sequence, use the Java Course: Concepts, MCQs & Coding Questions. To apply Java in interview data structures, continue with the DSA using Java course.