Skip to content

Serialization — Serializable, transient, ObjectOutputStream, readObject, and Versioning

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about Serialization. We cover key concepts, practical examples, and best practices to help you master this topic.

Java serialization converts objects into a byte stream for persistence or transmission, using the Serializable marker interface. Serialization is the JVM's built-in mechanism for object persistence — it flattens a graph of connected objects into a sequence of bytes that can be written to a file, sent over a network, or stored in a database.

What You'll Learn

  • Implementing Serializable
  • The serialization mechanism: ObjectOutputStream/ObjectInputStream
  • Transient fields and custom serialization
  • Versioning with serialVersionUID

Why It Matters

Serialization is the foundation of Java RMI, session replication in application servers, and many caching solutions. Understanding serialization prevents security vulnerabilities (deserialization attacks) and compatibility issues when classes evolve.

Real-World Use

HTTP sessions serialize user data for clustering. Spark distributes data across cluster nodes via serialization. RMI and EJB use serialization for remote method calls.


Basic Serialization

import java.io.Serializable;

public class Person implements Serializable {
    private String name;
    private int age;
    private String email;

    // constructor, getters, setters
}

Serializing:

Person person = new Person("Alice", 30, "alice@example.com");

try (ObjectOutputStream oos = new ObjectOutputStream(
        new FileOutputStream("person.ser"))) {
    oos.writeObject(person);
}

Deserializing:

try (ObjectInputStream ois = new ObjectInputStream(
        new FileInputStream("person.ser"))) {
    Person person = (Person) ois.readObject();
    System.out.println(person.getName());
}

The serialVersionUID

Every serializable class has a version identifier:

public class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    // ...
}

If you do not declare one, the JVM computes it from the class structure. This computed UID changes when you add or remove fields, causing InvalidClassException on deserialization.

Explicit UID Benefits

  • Backward compatibility — old serialized data can be read after adding fields
  • Deterministic — not dependent on compiler implementation
  • Performance — avoids computing at runtime

Transient Fields

Fields marked transient are skipped during serialization:

public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    private String username;
    private transient String password; // not serialized
    private transient Logger logger = Logger.getLogger(); // not serializable
}

When deserialized, transient fields receive their default values (null for objects, 0 for primitives).

Custom Serialization

Implement writeObject and readObject to customize the Process:

public class SecureData implements Serializable {
    private static final long serialVersionUID = 1L;
    private String data;
    private transient String encrypted;

    private void writeObject(ObjectOutputStream oos) throws IOException {
        oos.defaultWriteObject();
        oos.writeObject(encrypt(data)); // custom serialization
    }

    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        ois.defaultReadObject();
        this.encrypted = (String) ois.readObject();
        this.data = decrypt(this.encrypted);
    }

    private String encrypt(String s) { /* ... */ }
    private String decrypt(String s) { /* ... */ }
}

readResolve — Singleton Protection

Prevents deserialization from creating a new instance:

public class Singleton implements Serializable {
    private static final Singleton INSTANCE = new Singleton();
    private Singleton() {}

    public static Singleton getInstance() { return INSTANCE; }

    // Ensure single instance after deserialization
    private Object readResolve() {
        return INSTANCE;
    }
}

Inheritance and Serialization

  • If a parent class implements Serializable, all subclasses are serializable
  • If a parent class does NOT implement Serializable, its fields are NOT serialized
public class Animal {
    protected String species; // NOT serialized
}

public class Dog extends Animal implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
}

// When deserializing Dog:
// - name is restored from stream
// - species is NOT restored (the no-arg constructor of Animal is called)

Deserialization Security

Deserializing untrusted data is dangerous — it can trigger arbitrary code execution:

// Always validate what you deserialize
try (ObjectInputStream ois = new ObjectInputStream(inputStream)) {
    Object obj = ois.readObject();
    if (!(obj instanceof Person)) {
        throw new SecurityException("Unexpected type: " + obj.getClass());
    }
    Person person = (Person) obj;
}

Safe Deserialization Practices

  1. Use a whitelist of allowed classes
  2. Validate the object after deserialization
  3. Prefer JSON/XML over Java serialization for external data
  4. Consider using SerializationFilter (Java 9+): ObjectInputFilter.Config.setSerialFilter(filter)

Common Mistakes

  1. Not declaring serialVersionUID. Adding any field to a class without updating the UID breaks deserialization of old data.
  2. Serializing inner classes. Non-static inner classes have an implicit reference to the enclosing instance, which can cause unintentional serialization of large object graphs.
  3. Forgetting transient for non-serializable fields. If a field references a non-serializable object and is not transient, serialization throws NotSerializableException.
  4. Relying on default serialization for sensitive data. Transient passwords are not serialized, but all other fields are — including credit card numbers.
  5. Deserializing untrusted data. Deserialization attacks can execute arbitrary code. Never deserialize from untrusted sources.

Practice Questions

1. What is the purpose of serialVersionUID?
It identifies the version of a serializable class. During deserialization, the JVM compares the UID of the streamed class with the local class — mismatches cause InvalidClassException.

2. What does the transient keyword do?
It marks a field as non-serializable. During serialization, transient fields are skipped and restored to their default values.

3. How does readResolve() work?
After deserialization, if readResolve() is defined, the returned object replaces the deserialized instance. Used for singleton and enum patterns.

4. Why is Java serialization considered unsafe?
Malformed serialized data can trigger arbitrary code execution during deserialization. Libraries like ObjectInputStream can instantiate arbitrary classes and call methods on them.

5. What happens when a parent class does not implement Serializable?
The parent's fields are not serialized. During deserialization, the parent's no-arg constructor is called to initialize non-serialized fields.

Challenge Question:
Create a Configuration class that stores settings as a Map<String, String>. It should be serializable. Add custom serialization that encrypts sensitive keys (anything containing "password" or "secret") before serialization and decrypts after deserialization. Use transient for the logger. Write a unit test that serializes, deserializes, and verifies the configuration.

FAQ

Why does Serializable not have any methods?

Serializable is a marker interface. It tells the JVM that the class permits serialization. The actual serialization logic is implemented by ObjectOutputStream and ObjectInputStream.

What is the difference between Serializable and Externalizable?

Serializable uses default serialization (you can customize with writeObject/readObject). Externalizable requires implementing writeExternal() and readExternal() — giving you full control over the format.

Can I serialize a lambda?

Generally no. Lambda implementations are synthetic and may not be serializable. If you need serializable lambdas, cast to Serializable and ensure captured variables are serializable.

What is the size overhead of Java serialization?

Significant. A simple Person with two fields can take 100+ bytes (class name, UID, field descriptors, etc.). JSON or Protocol Buffers are much more compact.

How do I serialize a static field?

Static fields are not serialized — they belong to the class, not the instance. If you need to persist static state, include it as an instance field or use a separate serialization mechanism.

Mini Project

Write a program SerializationDemo.java that:

  1. Creates an Employee class implementing Serializable with fields id, name, salary, password (transient)
  2. Serializes an Employee to a file using ObjectOutputStream
  3. Deserializes it back and shows that password is null
  4. Adds custom writeObject/readObject that compresses salary data (store as int cents instead of double)
  5. Implements readResolve for a Company singleton
  6. Attempts to deserialize a corrupted/modified file and handles InvalidClassException
  7. Demonstrates that adding a field without updating serialVersionUID breaks deserialization

What's Next

Serialization stores objects, but many applications need key-value configuration. Lesson 44 covers Properties and Configuration — the Properties class, ResourceBundle for Internationalization, configuration files, and the Preferences API.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro