Web Analytics

Serialization

Intermediate ~15 min read

Serialization converts Java objects into byte streams for storage or transmission. Deserialization reconstructs objects from those bytes. This enables object persistence, network transfer, and caching.

Why Serialization?

Use Case Description
Persistence Save object state to files or databases
Network Transfer Send objects between JVMs (RMI, distributed systems)
Caching Store objects in memory caches (Redis, Memcached)
Deep Copy Clone objects by serializing and deserializing

The Serializable Interface

java.io.Serializable is a marker interface (no methods). Implementing it tells Java that this class can be serialized.

import java.io.Serializable;

public class User implements Serializable {
    // Recommended: Define a version ID for compatibility
    private static final long serialVersionUID = 1L;

    private String username;
    private String email;
    private int age;

    public User(String username, String email, int age) {
        this.username = username;
        this.email = email;
        this.age = age;
    }

    // Getters and setters...

    @Override
    public String toString() {
        return "User{username='" + username + "', email='" + email + "', age=" + age + "}";
    }
}
serialVersionUID: A unique version identifier for the class. If you modify the class and the UID changes, deserialization of old data will fail with InvalidClassException. Always define it explicitly!

Serializing Objects

Use ObjectOutputStream to write objects to a file:

import java.io.*;

public class SerializeDemo {
    public static void main(String[] args) {
        User user = new User("john_doe", "[email protected]", 25);

        // Serialize - save object to file
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream("user.ser"))) {

            oos.writeObject(user);
            System.out.println("Object serialized successfully!");

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Deserializing Objects

Use ObjectInputStream to read objects from a file:

import java.io.*;

public class DeserializeDemo {
    public static void main(String[] args) {
        // Deserialize - read object from file
        try (ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream("user.ser"))) {

            User user = (User) ois.readObject();
            System.out.println("Deserialized: " + user);

        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

The transient Keyword

Fields marked transient are excluded from serialization. Use this for:

  • Sensitive data (passwords, tokens)
  • Computed/derived values
  • Non-serializable objects
public class Account implements Serializable {
    private static final long serialVersionUID = 1L;

    private String username;
    private transient String password;     // NOT serialized
    private transient double cachedBalance; // NOT serialized

    public Account(String username, String password) {
        this.username = username;
        this.password = password;
    }
}

// After deserialization:
// - username = "john"
// - password = null (transient fields get default values)
// - cachedBalance = 0.0

Serializing Collections

import java.io.*;
import java.util.*;

public class SerializeCollection {
    public static void main(String[] args) throws Exception {
        List<User> users = new ArrayList<>();
        users.add(new User("alice", "[email protected]", 30));
        users.add(new User("bob", "[email protected]", 25));

        // Serialize the entire list
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream("users.ser"))) {
            oos.writeObject(users);
        }

        // Deserialize the list
        try (ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream("users.ser"))) {
            @SuppressWarnings("unchecked")
            List<User> loadedUsers = (List<User>) ois.readObject();
            loadedUsers.forEach(System.out::println);
        }
    }
}

Custom Serialization

Override writeObject and readObject for custom behavior:

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

    private String username;
    private transient String password;

    // Custom serialization - encrypt password before saving
    private void writeObject(ObjectOutputStream oos) throws IOException {
        oos.defaultWriteObject();  // Serialize non-transient fields
        oos.writeObject(encrypt(password));  // Manually write encrypted password
    }

    // Custom deserialization - decrypt password after loading
    private void readObject(ObjectInputStream ois)
            throws IOException, ClassNotFoundException {
        ois.defaultReadObject();  // Deserialize non-transient fields
        this.password = decrypt((String) ois.readObject());  // Decrypt password
    }

    private String encrypt(String data) { /* encryption logic */ }
    private String decrypt(String data) { /* decryption logic */ }
}

Inheritance and Serialization

// Parent class is NOT Serializable
class Person {
    String name;
    int age;

    // MUST have no-arg constructor for deserialization!
    public Person() {
        this.name = "Unknown";
        this.age = 0;
    }
}

// Child class IS Serializable
class Employee extends Person implements Serializable {
    private static final long serialVersionUID = 1L;
    String employeeId;

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

// After deserialization:
// - employeeId = "E123" (serialized)
// - name = "Unknown" (from parent's no-arg constructor)
// - age = 0 (from parent's no-arg constructor)
Rule: If a parent class is not Serializable, it MUST have a no-argument constructor. Parent fields won't be serialized and will be initialized via that constructor during deserialization.

Externalizable Interface

For complete control over serialization, implement Externalizable:

import java.io.*;

public class Product implements Externalizable {
    private String name;
    private double price;
    private transient int stockCount;

    // REQUIRED: public no-arg constructor
    public Product() {}

    public Product(String name, double price, int stockCount) {
        this.name = name;
        this.price = price;
        this.stockCount = stockCount;
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeUTF(name);
        out.writeDouble(price);
        // Don't write stockCount
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException {
        name = in.readUTF();
        price = in.readDouble();
        stockCount = 0;  // Default value
    }
}

Serialization vs Externalizable

Feature Serializable Externalizable
Control Automatic (default behavior) Full manual control
Performance Slower (uses reflection) Faster (direct read/write)
No-arg constructor Not required Required (public)
transient fields Supported You decide what to write

Security Considerations

Warning: Java serialization has known security vulnerabilities. Deserializing untrusted data can lead to remote code execution attacks.
  • Never deserialize data from untrusted sources
  • Consider using JSON (Jackson, Gson) or Protocol Buffers instead
  • Use serialization filters (Java 9+) to restrict allowed classes

Modern Alternatives

// JSON with Jackson (recommended for most use cases)
ObjectMapper mapper = new ObjectMapper();

// Serialize to JSON
String json = mapper.writeValueAsString(user);
// {"username":"john","email":"[email protected]","age":25}

// Deserialize from JSON
User user = mapper.readValue(json, User.class);

// JSON with Gson
Gson gson = new Gson();
String json = gson.toJson(user);
User user = gson.fromJson(json, User.class);
Output
Click Run to execute your code

Summary

  • Serializable: Marker interface for automatic serialization
  • serialVersionUID: Always define for version compatibility
  • transient: Exclude fields from serialization
  • ObjectOutputStream/ObjectInputStream: Write/read objects
  • Externalizable: Full control over serialization format
  • Security: Never deserialize untrusted data; consider JSON alternatives