What’s New ?

The Top 10 favtutor Features You Might Have Overlooked

Read More

Serialization in Java (with Codes)

  • Nov 24, 2023
  • 15 Minutes Read
Serialization in Java (with Codes)

Serialization is the process of converting an object into a sequence of bytes to persist or transmit it across various platforms in a portable form. It provides a convenient way to save the state of an object and recreate it later. In Java, it's crucial for object persistence and network communication. In this comprehensive guide, we will explore the ins and outs of serialization in Java, including its advantages, implementation details, and best practices.

What is Serialization in Java?

Serialization in Java is the process of converting an object's state into a byte stream. This byte stream includes the object's data as well as metadata about the object's type and the types of data stored in it. Serialization allows objects to be stored, transferred, or persisted, and provides a way to save and recreate the object's state at a later time. It is widely used in various areas such as Hibernate, JMS, JPA, and EJB.

Importance and Use Cases of Serialization

Let’s discuss some use cases of Serialization.

  • Object Persistence: Serialization allows objects to be stored in a non-volatile storage medium like files or databases.
  • Data Transmission: Enables the transfer of objects across a network between different Java applications.
  • Remote Method Invocation (RMI): Key for RMI, where objects residing in one JVM can be accessed by another.

How Serialization Works in Java?

  • Java's serialization process involves converting an object's state (fields and values) into a byte stream.

  • Serializable objects can be written to an ObjectOutputStream and read from an ObjectInputStream.

Serializable Interface and its Significance

The Serializable interface acts as a marker interface, indicating that a class's objects can be serialized. For a class to be serialized, it must implement this interface.

Example:

Here's an example demonstrating the basic serialization process:

import java.io.*;

class Person implements Serializable {
    String name;
    int age;

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

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

        try {
            FileOutputStream fileOut = new FileOutputStream("person.ser");
            ObjectOutputStream objectOut = new ObjectOutputStream(fileOut);

            objectOut.writeObject(person);

            objectOut.close();
            fileOut.close();
            System.out.println("Object has been serialized to person.ser");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 

The Person class implements Serializable.

An instance of Person is created and serialized using ObjectOutputStream to a file named person.ser.

Output:

Object has been serialized to person.ser

 

What is Serializable Interface?

The Serializable interface in Java is a marker interface. It acts as a signal to the Java Virtual Machine (JVM) that the class implementing it is eligible for serialization.

Implementing Serializable Interface

Implementing Serializable is simple. You just need to add the Serializable clause to the class definition. This interface acts as a marker, indicating that the class can be serialized. By implementing Serializable, a class agrees to have its state serialized and deserialized. All fields of the class are also serialized unless marked as transient.

Serialization Process for Serializable Objects

When an object of a class that implements Serializable is serialized:

  • The object's state (values of its fields) is converted into a stream of bytes.
  • This stream can be stored in a file, sent over a network, or stored in any other persistent storage.

Example:

Extending from the previous code, let's deserialize the Person object back from the file:

public class DeserializationExample {
    public static void main(String[] args) {
        try {
            FileInputStream fileIn = new FileInputStream("person.ser");
            ObjectInputStream objectIn = new ObjectInputStream(fileIn);

            Person deserializedPerson = (Person) objectIn.readObject();

            objectIn.close();
            fileIn.close();

            System.out.println("Deserialized Object:");
            System.out.println("Name: " + deserializedPerson.name);
            System.out.println("Age: " + deserializedPerson.age);

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

 

  • ObjectInputStream reads the serialized Person object from the file.
  • Casting the deserialized object to Person.
  • Printing out the serialized personal details.

Output:

Deserialize Object:

Name: Alice

Age: 30

 

Serialization and Deserialization

Let’s understand what is Serialization and Deserialization.

Understanding Serialization Process

  • Serialization Steps: Serialization involves converting an object's state into a sequence of bytes.
  • ObjectOutputStream: This class handles the serialization of objects in Java.
  • ObjectOutput: Allows writing objects to an output stream.

Deserialization Process

  • Deserialization Steps: Deserialization is the process of reconstructing objects from the serialized byte stream.
  • ObjectInputStream: Responsible for serializing objects in Java.
  • ObjectInput: Allows reading objects from an input stream.

Challenges and Considerations in Serialization/Deserialization

  • Versioning: Changes in class structures might lead to versioning issues during deserialization.
  • Security: Deserialization can post security risks if untrusted streams are deserialized.

Example:

Let's introduce versioning in our Person class:

class Person implements Serializable {
    private static final long serialVersionUID = 1L; // Version control
    String name;
    int age;

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

 

serialVersionUID controls the version of serialized objects. It helps with versioning and compatibility issues.

Serialization in Different Contexts

Serialization with Files: Saving and Retrieving Serialized Objects

  • File Streams: Use FileInputStream and FileOutputStream for reading and writing serialized objects to files.

Serialization with Streams: Object Input/Output Streams

  • Object Streams: Utilize ObjectInputStream and ObjectOutputStream to serialize and deserialize objects directly from/to streams.

Serialization in Networking: Transmitting Serialized Objects

  • Socket Communication: Serialize objects and transmit them over sockets using ObjectInputStream and ObjectInputStream.

Example:

Using networking for object transmission:

// Server side
try {
    ServerSocket serverSocket = new ServerSocket(8888);
    Socket socket = serverSocket.accept();
    ObjectOutputStream objectOut = new ObjectOutputStream(socket.getOutputStream());

    Person person = new Person("Bob", 25);
    objectOut.writeObject(person);

    objectOut.close();
    serverSocket.close();
} catch (IOException e) {
    e.printStackTrace();
}

// Client side
try {
    Socket socket = new Socket("localhost", 8888);
    ObjectInputStream objectIn = new ObjectInputStream(socket.getInputStream());

    Person receivedPerson = (Person) objectIn.readObject();
    System.out.println("Received Person: " + receivedPerson.name + ", Age: " + receivedPerson.age);

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

 

The server sends a serialized Person object over a socket.

The client receives and deserializes the object from the socket inputstream.

Customizing Serialization

Overriding Default Serialization Behavior

  • serialVersionUID: Controlling serialization versions using this field.
  • writeObject and read Object Methods: Customizing serialization by overriding these methods to provide custom serialization logic.

Controlling Serialization with transient and static Fields

  • transient Keyword: Prevents certain fields from being serialized.
  • static Fields: Not serialized as they belong to the class, not to instances.

Custom Serialization with readObject and writeObject Methods

  • Custom Logic: Implement custom serialization logic within these methods.
  • Handling Transient Fields: Use methods to handle transient fields during serialization and deserialization.

Example:

Implementing custom serialization using writeObject and readObject:

private void writeObject(ObjectOutputStream out) throws IOException {
    out.defaultWriteObject();
    out.writeInt(age * 2); // Custom serialization of age
}

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
    in.defaultReadObject();
    age = in.readInt() / 2; // Custom deserialization of age
}

 

writeObject customizes the serialization process  by writing the age multiplied by 2.

readObject customizes the deserialization process by dividing the age back to its original value.

Version Control in Serialization

Handling Versioning in Serialized Objects

  • serialVersionUID: Ensures version control of serialized objects.
  • Compatibility: Allows backward and forward compatibility of serialized objects across different versions of classes.

Managing Compatibility Issues

  • Adding Fields: Adding new fields to a class might cause deserialization issues if not handled properly.
  • Removing Fields: Removing fields from a class can cause compatibility problems with older serialized objects.

Example:

Handling versioning changes:

private static final long serialVersionUID = 2L; // Version change

// Inside readObject method
int version = in.readInt();
if (version == 1) {
    age = in.readInt();
} else if (version == 2) {
    // New version handling
}

 

Reading a version indicator during serialization and handling different versions accordingly.

Allows backward compatibility for different versions of serialized objects.

Security Risks in Serialization

  • Deserialization Vulnerabilities: Deserialization from untrusted sources can lead to security threats like remote code execution.
  • Externalizing Data: Externalize sensitive data or use encryption before serialization.

Best Practices for Secure Serialization

  • Validate Input Streams: Validate and sanitize input streams before deserialization.
  • Use Whitelisting: Implement whitelisting to only allow specific classes during deserialization.

Example:

Implementing whitelisting during deserialization:

class MyObjectInputStream extends ObjectInputStream {
    public MyObjectInputStream(InputStream in) throws IOException {
        super(in);
    }

    @Override
    protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
        if (!desc.getName().equals("AllowedClassName")) {
            throw new InvalidClassException("Unauthorized deserialization attempt", desc.getName());
        }
        return super.resolveClass(desc);
    }
}

 

Custom implementation of ObjectInputStream overrides resolveClass to allow only specific classes for deserialization.

Prevents unauthorized deserialization attempts by restricting allowed classes.

Real-world Applications and Examples of Serialization

Implementing Serialization in Practical Scenarios

  • Database Interaction: Serialization for storing and retrieving Java objects in databases.
  • Caching Mechanisms: Serialization in caching frameworks for object storage and retrieval.
  • Web Applications: Serialization for session management or storing application state.

Use Cases and Examples from Java APIs or Libraries

  • Java Collections: Serialization of Java collection classes like ArrayList, HashMap, etc.
  • Persistence Frameworks: Usage of serialization in persistence frameworks like Hibernate or JPA.

Example:

Using serialization in a caching mechanism:

// Serialization for storing and retrieving Java objects in a caching mechanism
public void storeObjectToCache(String key, Serializable object) {
    // Store the object using key in the cache
}

public Serializable getObjectFromCache(String key) {
    // Retrieve object from cache using key
    return null; // Return the deserialized object
}

 

Storing and retrieving objects using serialization in a caching mechanism.

Serialization allows objects to be stored and retrieved from cache efficiently.

Conclusion

Serialization in Java is a powerful mechanism enabling object persistence, network communication, and efficient storage and retrieval of data structures. Its significance lies in its ability to convert object states into portable byte streams, facilitating various functionalities across diverse applications. In this comprehensive guide, we have explored the concept of serialization in Java, its advantages, implementation details, and real world applications.

FavTutor - 24x7 Live Coding Help from Expert Tutors!

About The Author
Nihal Mahansaria
As a computer science student with a passion for technology and a drive to learn, I have developed a diverse set of skills in web development, machine learning, and technical content writing. With experience in multiple front-end and back-end frameworks, including Flask and Python, I am able to create scalable and efficient web applications.