Java Serialization

Clean HTML study notes — Serialization, Deserialization, Object Graph, transient, Inheritance, Externalization and serialVersionUID

Prepared from: cj14-serialization.pdf
Converted into the previous clean HTML study-notes format. Original PDF Pages are intentionally not included.

1. Serialization — Introduction

Serialization is the process of converting an object into a byte stream so that the object state can be stored or transmitted.

  • Save an object to a file or database.
  • Transfer an object over a network.
  • Cache or persist object state.

Deserialization is the reverse process: converting the byte stream back into an object.

How to enable serialization

  1. The class must implement java.io.Serializable, which is a marker interface.
  2. A serialVersionUID can optionally be declared for version compatibility.
import java.io.Serializable;

class Student implements Serializable {
    private static final long serialVersionUID = 1L;

    int id;
    String name;
}
Important: Static fields and transient fields are not serialized automatically. The transient keyword is commonly used for sensitive or irrelevant instance fields such as passwords.

2. Serialization and Deserialization Order

Multiple objects can be serialized to the same stream. When reading them back, they must be deserialized in the same logical order in which they were written.

ObjectOutputStream oos =
    new ObjectOutputStream(new FileOutputStream("abc.ser"));

oos.writeObject(dog);
oos.writeObject(cat);
oos.writeObject(rat);

ObjectInputStream ois =
    new ObjectInputStream(new FileInputStream("abc.ser"));

Dog  d = (Dog) ois.readObject();
Cat  c = (Cat) ois.readObject();
Rat  r = (Rat) ois.readObject();
write Dogwrite Catwrite Ratread Dogread Catread Rat

3. Basic Serialization Example

A class that implements Serializable can be written using ObjectOutputStream and restored using ObjectInputStream.

class Cat implements Serializable {
    int i = 11;
}

class Dog implements Serializable {
    int j = 12;
}

class Rat implements Serializable {
    int k = 13;
}

// Serialization
FileOutputStream fos = new FileOutputStream("abc.ser");
ObjectOutputStream oos = new ObjectOutputStream(fos);

oos.writeObject(new Dog());
oos.writeObject(new Cat());
oos.writeObject(new Rat());

// Deserialization
FileInputStream fis = new FileInputStream("abc.ser");
ObjectInputStream ois = new ObjectInputStream(fis);

Dog d2 = (Dog) ois.readObject();
Cat c2 = (Cat) ois.readObject();
Rat r2 = (Rat) ois.readObject();

System.out.println(d2.j);
System.out.println(c2.i);
System.out.println(r2.k);

The example produces values corresponding to the fields of the restored Dog, Cat and Rat objects.

4. Object Graph in Serialization

An Object Graph is the set of all objects reachable from the object being serialized. During default serialization, reachable serializable objects are automatically included in the serialized graph.

  • If a Dog object refers to a Cat object and the Cat refers to a Rat object, serializing Dog can serialize the reachable Cat and Rat objects as well.
  • Every object that becomes part of the serialized graph must be serializable.
  • If a reachable object is not serializable, serialization can fail with NotSerializableException.
Dog → Cat → Rat
All reachable objects form the Object Graph
class Dog1 implements Serializable {
    Cat1 c1 = new Cat1();
}

class Cat1 implements Serializable {
    Rat1 r1 = new Rat1();
}

class Rat1 implements Serializable {
    int k = 13;
}

// Serializing Dog1 also serializes its reachable
// Cat1 and Rat1 objects.

5. Transient Keyword

The transient modifier can be applied to an instance field to exclude that field from default serialization.

class Account implements Serializable {
    String userName = "sri";
    transient String pwd = "sai";
}

Before serialization, the Account object can contain both username and password. After default deserialization, the transient password is not restored and therefore receives its default value, such as null for a String.

FieldBefore serializationAfter default deserialization
userName"sri""sri"
pwd (transient)"sai"null
Why use transient? It is useful for passwords, temporary state, derived values, caches and other data that should not be stored as part of the serialized state.

6. Customized Serialization — writeObject() and readObject()

Default serialization can cause loss of transient information. Customized serialization allows a class to control how its state is written and restored.

Two special private methods are used:

  • private void writeObject(ObjectOutputStream out) — called automatically during serialization.
  • private void readObject(ObjectInputStream in) — called automatically during deserialization.

These are commonly called callback methods because the JVM invokes them automatically when the serialization process reaches that class.

class Account implements Serializable {
    String userName = "sri";
    transient String pwd = "sai";

    private void writeObject(ObjectOutputStream out)
            throws IOException {
        out.defaultWriteObject();
        out.writeObject(pwd);       // custom handling
    }

    private void readObject(ObjectInputStream in)
            throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        pwd = (String) in.readObject(); // restore
    }
}

With customized serialization, the Account object can restore the password even though the field itself is marked transient.

7. Why JVM Can Invoke Private Serialization Methods

Program code normally cannot directly call a private method from outside its class. Serialization is special because the JVM/serialization mechanism uses reflection-like runtime access to invoke the appropriately named private callback methods.

The programmer does not normally call writeObject() or readObject() directly. They are triggered by ObjectOutputStream and ObjectInputStream.

8. Serialization with Inheritance — Case 1

If a parent class implements Serializable, its serializable nature is inherited by child classes. Therefore a child object can be serialized even if the child class does not explicitly declare implements Serializable.

class Animal implements Serializable {
    int i = 10;
}

class Dog extends Animal {
    int j = 20;
}

// Dog is serializable because Animal is serializable.
Dog d1 = new Dog();
d1.i = 888;
d1.j = 999;
Animal implements Serializable

Dog extends Animal → Dog object can be serialized

This is the first important inheritance case described in the source material. fileciteturn14file1L48-L51

9. Serialization with Inheritance — Case 2

A child class can also be serializable even when its parent class is not serializable.

class Animal {
    int i = 10;
}

class Dog extends Animal implements Serializable {
    int j = 20;
}
  • The Dog-specific serializable state is restored from the stream.
  • The non-serializable parent's instance state is not obtained from the serialized stream in the normal way.
  • During deserialization, the first non-serializable superclass's no-argument constructor is invoked to initialize that part of the object.
  • Therefore the first non-serializable superclass must have an accessible no-argument constructor.
  • If such a constructor is unavailable, deserialization can fail with InvalidClassException.
Important: Object itself does not implement Serializable.

10. Non-Serializable Parent — Constructor Behavior

When a serializable child has a non-serializable parent, the parent portion is initialized during deserialization through the constructor of the first non-serializable superclass.

class Animal {
    int i = 10;

    public Animal() {
        // no-arg constructor
    }
}

class Dog extends Animal implements Serializable {
    int j = 20;
}

The source example demonstrates that the serialized child state can retain its values while the non-serializable parent's state is initialized through its constructor/default state.

If the non-serializable parent defines only a parameterized constructor and no accessible no-argument constructor, deserialization can result in an InvalidClassException. fileciteturn14file1L48-L51

11. Externalization — Introduction

Externalization is an alternative to standard serialization that gives the programmer full control over the serialization process.

With Serializable, Java performs default field handling. With Externalizable, the programmer explicitly defines what and how to write and read.

Interface

public interface Externalizable
        extends Serializable {

    void writeExternal(ObjectOutput out)
        throws IOException;

    void readExternal(ObjectInput in)
        throws IOException, ClassNotFoundException;
}

12. Serializable vs Externalizable

FeatureSerializableExternalizable
Control over processAutomatic/default behaviorManual custom read/write logic
PerformanceCan be slower for large/complex objectsCan be faster with carefully optimized logic
Interface methodsMarker interface; no methods to implementRequires writeExternal() and readExternal()
No-argument constructorNot generally required for a serializable classPublic no-arg constructor is required for normal externalization
Field selectionDefault mechanism handles serializable state; customization is possibleProgrammer explicitly chooses what to write/read

To use Externalization

  1. Implement Externalizable.
  2. Override writeExternal() and readExternal().
  3. Define manually which fields are written and read.
  4. Provide a public no-argument constructor.

13. Externalizable Example

import java.io.*;

public class Employee implements Externalizable {

    private String name;
    private int age;

    // Public no-arg constructor is required
    public Employee() {
    }

    public Employee(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // Manual serialization
    @Override
    public void writeExternal(ObjectOutput out)
            throws IOException {
        out.writeObject(name);
        out.writeInt(age);
    }

    // Manual deserialization
    @Override
    public void readExternal(ObjectInput in)
            throws IOException, ClassNotFoundException {
        name = (String) in.readObject();
        age = in.readInt();
    }

    @Override
    public String toString() {
        return "Employee{name='" + name + "', age=" + age + "}";
    }
}
Employee objectwriteExternal()byte streamreadExternal()Employee object

14. Serializable vs Externalizable — Quick Comparison

SerializationExternalization
Designed for default serialization.Designed for customized serialization.
JVM handles the default field process.Programmer controls the fields written/read.
Normally the whole serializable object state is handled by the default mechanism.Only the state explicitly written can be restored.
Can have lower performance for large objects depending on the state.Can provide better performance when optimized.
Serializable has no methods.Externalizable requires two methods.
No public no-arg constructor requirement solely because of Serializable.Public no-arg constructor is required for Externalizable deserialization.
transient plays a role in default serialization.Field selection is explicitly controlled by writeExternal().

15. Use Cases of Externalization

  • Custom serialization logic.
  • Encrypting or transforming data before it is written.
  • Compressing selected fields.
  • Selectively including or excluding fields.
  • Improving performance for large objects or collections when custom logic is more efficient.

16. serialVersionUID

serialVersionUID is a unique version identifier for a serializable class. It is used during deserialization to check compatibility between the class definition used for serialization and the current class definition.

private static final long serialVersionUID = 1L;

How it works

  1. The object is serialized together with serialization metadata including the class's serialVersionUID.
  2. During deserialization, the JVM checks the UID against the currently loaded class.
  3. If the values are compatible/match, deserialization can proceed.
  4. If they do not match, InvalidClassException can be thrown.
Serialize → store class version UID
Deserialize → compare stored UID with current UID
Match → continue    Mismatch → InvalidClassException

17. Static vs Transient

Static fields belong to the class rather than to an individual object instance. Default object serialization focuses on instance state, so static fields are not serialized as part of each object.

Because static fields are already excluded from default object serialization, declaring a static field as transient is redundant.

FieldDefault serializationReason
Instance fieldSerialized unless transientPart of object state.
Static fieldNot serializedBelongs to the class, not an individual instance.
Transient instance fieldNot serializedExplicitly excluded by transient.

18. Final vs Transient

An instance field can technically be declared both final and transient. However, this combination requires care because a transient field is not restored from the stream, while a final field normally needs to be initialized.

class Example implements Serializable {
    transient final String value = "data";
}

When transient final state must be reconstructed during deserialization, custom serialization logic may be required. The source material highlights the difficulty of restoring a final field that was intentionally excluded from the serialized state.

19. Transient, Static and Final — Example

class X implements Serializable {

    transient static String transientStaticVar =
        "transientStaticVar";

    transient final String transientFinalVar =
        "transientFinalVar";

    transient static final String transientStaticFinalVar =
        "transientStaticFinalVar";

    transient String transientVar = "transientVar";

    static {
        transientStaticVar = "transientStaticVar";
        transientStaticFinalVar = "transientStaticFinalVar";
    }

    {
        // instance initialization
    }
}

Observed behavior from the source example

FieldAfter deserialization in the example
transient static variableUses the current static value rather than serialized object state.
transient final instance variableRequires careful initialization because it is transient and final.
transient static final variableNot restored as an instance field; static state belongs to the class.
transient instance variableReceives its default value, such as null for String.

20. Quick Revision

TopicKey point
SerializationConverts an object into a byte stream.
DeserializationReconstructs an object from a byte stream.
SerializableMarker interface used to enable default serialization.
Object GraphSet of reachable objects serialized along with the root object.
TransientPrevents an instance field from default serialization.
StaticNot serialized as part of an individual object's default state.
writeObject()Custom callback for serialization.
readObject()Custom callback for deserialization.
ExternalizableProvides explicit manual serialization/deserialization control.
writeExternal()Manually writes the selected externalized state.
readExternal()Manually restores the selected externalized state.
serialVersionUIDVersion identifier used for serializable class compatibility checks.
Non-serializable parentIts state is initialized through its constructor during child deserialization.
InvalidClassExceptionCan occur for incompatible serialVersionUID or missing required no-arg constructor in a non-serializable superclass.