Skip to content

Java Serialization

Java Object Serialization is the process of converting an object’s state into a sequence of bytes. This byte stream includes the object’s data, information about its type, and the types of data stored within it. Deserialization is the reverse process: recreating the object in memory from the byte stream.

  • Persist object state to a file or database.
  • Transfer objects over a network (e.g., in Remote Method Invocation - RMI).
  • Distribute objects between different Java Virtual Machines (JVMs).

A key feature is platform independence: an object can be serialized on one platform (e.g., Windows) and deserialized on another (e.g., Linux), provided the class definition is available on both.

Core Classes:

  • java.io.ObjectOutputStream: Writes serialized objects to an OutputStream (like FileOutputStream). Key method: writeObject(Object obj).
  • java.io.ObjectInputStream: Reads serialized objects from an InputStream (like FileInputStream). Key method: readObject(), which returns an Object (requires casting).

For a class to be eligible for serialization, it must implement the java.io.Serializable marker interface. This interface has no methods; it simply signals to the JVM that objects of the class can be serialized.

Conditions for Successful Serialization:

  • The class must implement java.io.Serializable (or its parent must).
  • All non-static, non-transient fields in the class must either be of primitive types or be serializable themselves. If a field’s type is not serializable, that field must be marked transient.

Use the transient keyword to indicate that a field should not be included in the serialization process. This is useful for sensitive data (like passwords), calculated fields, or fields referencing non-serializable resources.

During deserialization, the JVM checks if the serialVersionUID of the loaded class matches the serialVersionUID stored in the serialized object data. This ensures compatibility between the class version used for serialization and the one used for deserialization.

It is strongly recommended to explicitly declare a private static final long serialVersionUID in every Serializable class. If omitted, the compiler generates one based on the class structure, which can change unexpectedly, leading to InvalidClassException during deserialization if the class evolves.

private static final long serialVersionUID = 1L; // Example explicit UID

Example: Serializing and Deserializing an Employee Object

Section titled “Example: Serializing and Deserializing an Employee Object”

Consider this Employee class:

import java.io.Serializable;
public class Employee implements Serializable {
// Explicit serialVersionUID for version control
private static final long serialVersionUID = 1L;
public String name;
public String address;
// SSN marked transient - will not be serialized
public transient int SSN;
public int number;
// Default constructor (often needed for deserialization frameworks, though not strictly for basic Java serialization)
public Employee() {}
public Employee(String name, String address, int SSN, int number) {
this.name = name;
this.address = address;
this.SSN = SSN;
this.number = number;
}
public void mailCheck() {
System.out.println("Mailing a check to " + name + " at " + address);
}
@Override
public String toString() {
return "Employee [name=" + name + ", address=" + address + ", SSN(transient)=" + SSN + ", number=" + number + "]";
}
}

This program creates an Employee object and saves its state to a file named employee.ser using ObjectOutputStream and try-with-resources.

import java.io.*;
public class SerializeDemo {
public static void main(String[] args) {
Employee e = new Employee("Reyan Ali", "Phokka Kuan, Ambehta Peer", 11122333, 101);
String filename = "employee.ser"; // Use .ser convention
// Use try-with-resources to ensure streams are closed
try (FileOutputStream fileOut = new FileOutputStream(filename);
ObjectOutputStream out = new ObjectOutputStream(fileOut)) {
out.writeObject(e); // Serialize the object
System.out.println("Serialized data is saved in " + filename);
} catch (IOException i) {
System.err.println("Serialization error: " + i.getMessage());
i.printStackTrace();
}
}
}

This program reads the employee.ser file and reconstructs the Employee object using ObjectInputStream and try-with-resources.

import java.io.*;
public class DeserializeDemo {
public static void main(String[] args) {
Employee e = null;
String filename = "employee.ser";
// Use try-with-resources for input streams
try (FileInputStream fileIn = new FileInputStream(filename);
ObjectInputStream in = new ObjectInputStream(fileIn)) {
// Deserialize and cast the object
e = (Employee) in.readObject();
} catch (IOException i) {
System.err.println("Deserialization IO error: " + i.getMessage());
i.printStackTrace();
return;
} catch (ClassNotFoundException c) {
System.err.println("Employee class not found during deserialization.");
c.printStackTrace();
return;
}
System.out.println("Deserialized Employee...");
System.out.println(e); // Uses the toString() method
// Demonstrate transient field behavior
System.out.println("Deserialized SSN (transient): " + e.SSN);
System.out.println("Number: " + e.number);
e.mailCheck();
}
}

Combined Output (Serialization then Deserialization):

Serialized data is saved in employee.ser
Deserialized Employee...
Employee [name=Reyan Ali, address=Phokka Kuan, Ambehta Peer, SSN(transient)=0, number=101]
Deserialized SSN (transient): 0
Number: 101
Mailing a check to Reyan Ali at Phokka Kuan, Ambehta Peer

Key Observations:

  • The try-with-resources statement simplifies resource management, automatically closing streams.
  • The readObject() method can throw ClassNotFoundException if the JVM cannot find the corresponding class definition.
  • The return value of readObject() is Object, requiring an explicit cast to the expected type (Employee).
  • The transient field SSN was not saved during serialization, so its value defaults to 0 (the default for int) after deserialization.

Deserializing data from untrusted sources poses significant security risks (Deserialization Vulnerabilities). Maliciously crafted byte streams can lead to arbitrary code execution. For data exchange, especially across trust boundaries, consider safer alternatives:

  • JSON: Use libraries like Jackson or Gson to serialize objects to/from JSON format. More human-readable and generally safer to parse.
  • Protocol Buffers (Protobuf) / FlatBuffers: Schema-based binary formats designed for efficiency and cross-language compatibility.
  • Database Persistence: Use JPA (Java Persistence API) or JDBC to store object data in relational or NoSQL databases.

If you must use Java serialization, consider implementing object validation during deserialization (e.g., overriding readObject carefully) or using filtering mechanisms (ObjectInputFilter).