Java > Java Input/Output (I/O) > Serialization and Deserialization > ObjectInputStream and ObjectOutputStream

Serialization and Deserialization with Object Streams

This example demonstrates how to serialize and deserialize Java objects using `ObjectOutputStream` and `ObjectInputStream`. Serialization converts an object into a byte stream, which can be stored or transmitted, and deserialization reconstructs the object from the byte stream.

Introduction to Serialization

Serialization is the process of converting an object's state into a byte stream. Deserialization is the reverse process, reconstructing the object from the byte stream. These processes are crucial for tasks like saving object states to disk, transmitting objects over a network, or caching objects.

The `Serializable` Interface

For an object to be serializable, its class must implement the `java.io.Serializable` interface. This interface is a marker interface, meaning it doesn't declare any methods. It signals to the JVM that instances of the class can be serialized. Any fields you don't want to be serialized should be marked as `transient`.

Person Class (Serializable)

This `Person` class implements `Serializable`. The `secret` field is marked as `transient`, meaning it won't be serialized. When the `Person` object is deserialized, the `secret` field will be null (or its default value for its type).

import java.io.Serializable;

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

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

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public String getSecret() {
        return secret;
    }

    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + ", secret='" + secret + "'}";
    }
}

Serialization Example

This code demonstrates both serialization and deserialization. First, a `Person` object is created. Then: 1. **Serialization:** A `FileOutputStream` is created to write to the file "person.ser". An `ObjectOutputStream` is wrapped around the `FileOutputStream` to enable object serialization. The `writeObject()` method of `ObjectOutputStream` serializes the `person` object to the file. 2. **Deserialization:** A `FileInputStream` is created to read from the file "person.ser". An `ObjectInputStream` is wrapped around the `FileInputStream` to enable object deserialization. The `readObject()` method of `ObjectInputStream` deserializes the object from the file. The result is cast back to a `Person` object. The output will show the serialized message and then the deserialized `Person` object. Note that the `secret` field will be `null` in the deserialized object because it was marked as `transient`.

import java.io.*;

public class SerializationExample {
    public static void main(String[] args) {
        Person person = new Person("Alice", 30, "MySecret");
        String filename = "person.ser";

        // Serialization
        try (FileOutputStream fileOut = new FileOutputStream(filename);
             ObjectOutputStream out = new ObjectOutputStream(fileOut)) {
            out.writeObject(person);
            System.out.println("Serialized data is saved in " + filename);

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

        // Deserialization
        try (FileInputStream fileIn = new FileInputStream(filename);
             ObjectInputStream in = new ObjectInputStream(fileIn)) {
            Person deserializedPerson = (Person) in.readObject();
            System.out.println("Deserialized Person: " + deserializedPerson);

        } catch (IOException i) {
            i.printStackTrace();
        } catch (ClassNotFoundException c) {
            System.out.println("Person class not found");
            c.printStackTrace();
        }
    }
}

Concepts Behind the Snippet

The core concepts are:

  • Serialization: Converting an object to a stream of bytes for storage or transmission.
  • Deserialization: Reconstructing an object from a stream of bytes.
  • `Serializable` Interface: Marker interface indicating a class is serializable.
  • `transient` Keyword: Used to exclude fields from serialization.
  • `ObjectOutputStream`: Writes primitive data types and Java objects to an output stream.
  • `ObjectInputStream`: Reads primitive data types and Java objects from an input stream.

Real-Life Use Case

Serialization is used in several scenarios:

  • Saving Game State: Saving a player's progress in a game.
  • Distributed Systems: Sending objects between different parts of a distributed application (e.g., using Remote Method Invocation - RMI).
  • Caching: Storing frequently accessed objects in a cache.
  • Persistence: Storing objects in a database.

Best Practices

  • Version Control: When modifying a serializable class, consider using `serialVersionUID` to maintain compatibility with older serialized versions. If you don't explicitly define it, the JVM will generate it automatically, which can lead to `InvalidClassException` during deserialization if the class structure changes.
  • Security: Be mindful of security vulnerabilities during deserialization, especially when dealing with untrusted data. Deserialization can be exploited to execute arbitrary code.
  • Transient Fields: Use `transient` keyword appropriately to exclude sensitive data from serialization.
  • Consider alternatives: For complex data structures or inter-system communication, consider using formats like JSON or Protocol Buffers which are often more efficient and easier to evolve.

Interview Tip

Be prepared to explain the difference between serialization and deserialization. Understand the purpose of the `Serializable` interface and the `transient` keyword. Be aware of the potential security risks associated with deserialization and how to mitigate them.

When to use them

Use serialization/deserialization when you need to:

  • Persist object state to disk.
  • Transmit objects over a network.
  • Cache objects.
Consider alternatives like JSON or Protocol Buffers for inter-system communication and data exchange, especially when dealing with different programming languages.

Memory Footprint

The memory footprint of a serialized object depends on the size of the object's data and the serialization format. Serialization can increase the size of the data due to metadata overhead. Transient fields reduce the memory footprint of the serialized data. When deserializing, the JVM needs enough memory to reconstruct the entire object graph.

Alternatives

Alternatives to Java serialization include:

  • JSON (Jackson, Gson): Human-readable format, suitable for web APIs and data exchange.
  • Protocol Buffers: Language-neutral, platform-neutral, extensible mechanism for serializing structured data. More efficient than Java serialization.
  • XML (JAXB): Another human-readable format, often used for configuration files and data exchange.
  • Kryo: Fast and efficient Java serialization library.

Pros

Pros of Java Serialization:

  • Built-in support in Java.
  • Easy to use for simple objects.

Cons

Cons of Java Serialization:

  • Performance overhead compared to other serialization formats.
  • Security vulnerabilities.
  • Versioning issues can arise when the class structure changes.
  • Can be verbose.

FAQ

  • What happens if a class doesn't implement `Serializable`?

    If you try to serialize an object of a class that doesn't implement `Serializable`, you'll get a `NotSerializableException`.
  • What is `serialVersionUID` and why is it important?

    `serialVersionUID` is a unique identifier for a serializable class. It's used during deserialization to verify that the serialized data is compatible with the current class version. If the `serialVersionUID` of the serialized object doesn't match the `serialVersionUID` of the class, an `InvalidClassException` is thrown. It's crucial for maintaining compatibility across different versions of a class.
  • What is the purpose of the `transient` keyword?

    The `transient` keyword is used to prevent a field from being serialized. Transient fields are not saved as part of the object's serialized state. They're typically used for fields that are derived, contain sensitive information, or should not be persisted.