Table of Contents
- Introduction to Serialization
- Serialization Interface
- ObjectOutputStream
- ObjectInputStream
- transient Keyword
- serialVersionUID
- Custom Serialization
- Inheritance and Serialization
- Externalizable Interface
- Security Concerns
- Best Practices
- Examples
Introduction to Serialization
Serialization is the process of converting an object into a byte stream to store the object or transmit it to memory, a database, or a file. Deserialization is the reverse process - reconstructing the object from the byte stream.
Key Concepts:
- Persistence: Save object state for later use
- Network Transmission: Send objects over network
- Deep Copy: Create copies of objects
- Distributed Computing: Share objects between JVMs
Serialization Interface
The java.io.Serializable interface is a marker interface (no methods) that indicates a class can be serialized.
import java.io.Serializable;
public class Person implements Serializable {
private String name;
private int age;
private String email;
// Constructors, getters, setters
public Person(String name, int age, String email) {
this.name = name;
this.age = age;
this.email = email;
}
// Getters and setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + ", email='" + email + "'}";
}
}
ObjectOutputStream
ObjectOutputStream is used to serialize objects to an output stream.
Key Methods:
writeObject(Object obj)- Serializes an objectflush()- Flushes the streamclose()- Closes the stream
import java.io.*;
public class SerializationDemo {
public static void serializeObject(Object obj, String filename) {
try (FileOutputStream fileOut = new FileOutputStream(filename);
ObjectOutputStream out = new ObjectOutputStream(fileOut)) {
out.writeObject(obj);
System.out.println("Object serialized successfully: " + filename);
} catch (IOException e) {
System.err.println("Serialization error: " + e.getMessage());
}
}
}
ObjectInputStream
ObjectInputStream is used to deserialize objects from an input stream.
Key Methods:
readObject()- Deserializes an objectclose()- Closes the stream
public class DeserializationDemo {
public static Object deserializeObject(String filename) {
try (FileInputStream fileIn = new FileInputStream(filename);
ObjectInputStream in = new ObjectInputStream(fileIn)) {
Object obj = in.readObject();
System.out.println("Object deserialized successfully: " + filename);
return obj;
} catch (IOException | ClassNotFoundException e) {
System.err.println("Deserialization error: " + e.getMessage());
return null;
}
}
}
transient Keyword
The transient keyword prevents fields from being serialized.
import java.io.Serializable;
public class User implements Serializable {
private String username;
private transient String password; // Won't be serialized
private transient Session currentSession; // Transient reference
public User(String username, String password) {
this.username = username;
this.password = password;
this.currentSession = new Session(); // This won't be serialized
}
// Getters and setters
public String getUsername() { return username; }
public String getPassword() { return password; } // Will be null after deserialization
@Override
public String toString() {
return "User{username='" + username + "', password='" +
(password != null ? "***" : "null") + "'}";
}
}
class Session {
// This class doesn't implement Serializable
}
serialVersionUID
The serialVersionUID is a version control mechanism for serialization.
import java.io.Serializable;
public class Employee implements Serializable {
// Explicitly declare serialVersionUID for version control
private static final long serialVersionUID = 1L;
private String name;
private int employeeId;
private String department;
// Version 1: Only basic fields
public Employee(String name, int employeeId, String department) {
this.name = name;
this.employeeId = employeeId;
this.department = department;
}
// Getters and setters
public String getName() { return name; }
public int getEmployeeId() { return employeeId; }
public String getDepartment() { return department; }
@Override
public String toString() {
return "Employee{name='" + name + "', id=" + employeeId +
", department='" + department + "'}";
}
}
// Updated version of the class
class EmployeeV2 implements Serializable {
private static final long serialVersionUID = 2L; // Updated version
private String name;
private int employeeId;
private String department;
private double salary; // New field added
public EmployeeV2(String name, int employeeId, String department, double salary) {
this.name = name;
this.employeeId = employeeId;
this.department = department;
this.salary = salary;
}
// Getters and setters
public double getSalary() { return salary; }
public void setSalary(double salary) { this.salary = salary; }
@Override
public String toString() {
return "EmployeeV2{name='" + name + "', id=" + employeeId +
", department='" + department + "', salary=" + salary + "}";
}
}
Custom Serialization
You can customize serialization by implementing writeObject() and readObject() methods.
import java.io.*;
public class BankAccount implements Serializable {
private static final long serialVersionUID = 1L;
private String accountNumber;
private transient String accountHolder; // Custom serialization
private double balance;
private transient String sensitiveData;
public BankAccount(String accountNumber, String accountHolder, double balance) {
this.accountNumber = accountNumber;
this.accountHolder = accountHolder;
this.balance = balance;
this.sensitiveData = "Confidential: " + accountNumber;
}
// Custom serialization logic
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject(); // Serialize non-transient fields
// Custom serialization for transient fields
out.writeObject(accountHolder != null ? accountHolder : "Unknown");
// Encrypt sensitive data before serialization
String encrypted = encrypt(sensitiveData);
out.writeObject(encrypted);
}
// Custom deserialization logic
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject(); // Deserialize non-transient fields
// Custom deserialization for transient fields
accountHolder = (String) in.readObject();
// Decrypt sensitive data after deserialization
String encrypted = (String) in.readObject();
sensitiveData = decrypt(encrypted);
}
private String encrypt(String data) {
// Simple encryption for demonstration
return new StringBuilder(data).reverse().toString();
}
private String decrypt(String data) {
// Simple decryption for demonstration
return new StringBuilder(data).reverse().toString();
}
// Getters and setters
public String getAccountNumber() { return accountNumber; }
public String getAccountHolder() { return accountHolder; }
public double getBalance() { return balance; }
public String getSensitiveData() { return sensitiveData; }
@Override
public String toString() {
return "BankAccount{accountNumber='" + accountNumber +
"', accountHolder='" + accountHolder +
"', balance=" + balance + "}";
}
}
Inheritance and Serialization
Serialization with Inheritance
import java.io.*;
class Person implements Serializable {
private static final long serialVersionUID = 1L;
protected String name;
protected int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person() {
// No-arg constructor for deserialization
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
class Student extends Person {
private static final long serialVersionUID = 1L;
private String studentId;
private transient double gpa; // Transient field
public Student(String name, int age, String studentId, double gpa) {
super(name, age);
this.studentId = studentId;
this.gpa = gpa;
}
// Custom serialization for Student
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject(); // Serialize Student fields
// Parent class fields are automatically serialized
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject(); // Deserialize Student fields
// Parent class fields are automatically deserialized
}
@Override
public String toString() {
return "Student{name='" + name + "', age=" + age +
", studentId='" + studentId + "', gpa=" + gpa + "}";
}
}
class Professor extends Person {
private static final long serialVersionUID = 1L;
private String department;
private double salary;
public Professor(String name, int age, String department, double salary) {
super(name, age);
this.department = department;
this.salary = salary;
}
@Override
public String toString() {
return "Professor{name='" + name + "', age=" + age +
", department='" + department + "', salary=" + salary + "}";
}
}
Externalizable Interface
The Externalizable interface provides complete control over serialization.
import java.io.*;
public class Product implements Externalizable {
private String name;
private double price;
private int quantity;
private transient String internalCode; // Won't be serialized by default
// Required public no-arg constructor for Externalizable
public Product() {
System.out.println("No-arg constructor called");
}
public Product(String name, double price, int quantity) {
this.name = name;
this.price = price;
this.quantity = quantity;
this.internalCode = "CODE-" + name.toUpperCase();
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
// Complete control over what gets written
out.writeUTF(name);
out.writeDouble(price);
out.writeInt(quantity);
// We can choose to serialize transient fields
out.writeUTF(internalCode != null ? internalCode : "UNKNOWN");
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
// Complete control over what gets read
this.name = in.readUTF();
this.price = in.readDouble();
this.quantity = in.readInt();
this.internalCode = in.readUTF();
}
// Getters and setters
public String getName() { return name; }
public double getPrice() { return price; }
public int getQuantity() { return quantity; }
public String getInternalCode() { return internalCode; }
@Override
public String toString() {
return "Product{name='" + name + "', price=" + price +
", quantity=" + quantity + ", internalCode='" + internalCode + "'}";
}
}
Security Concerns
Security Best Practices
import java.io.*;
import java.security.*;
public class SecureSerialization {
// Example of validating objects during deserialization
public static class SecureObject implements Serializable {
private static final long serialVersionUID = 1L;
private String data;
public SecureObject(String data) {
this.data = data;
}
// Validate object state after deserialization
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException {
in.defaultReadObject();
validateObject();
}
private void validateObject() {
if (data == null || data.trim().isEmpty()) {
throw new SecurityException("Invalid object state: data cannot be null or empty");
}
// Add more validation as needed
}
public String getData() { return data; }
}
// Using object filtering (Java 9+)
public static class ObjectInputFilterDemo {
public static Object deserializeWithFilter(String filename) throws IOException, ClassNotFoundException {
try (FileInputStream fileIn = new FileInputStream(filename);
ObjectInputStream in = new ObjectInputStream(fileIn)) {
// Set filter to allow only specific classes
in.setObjectInputFilter(filterInfo -> {
Class<?> clazz = filterInfo.serialClass();
if (clazz != null) {
if (clazz.getName().startsWith("java.") ||
clazz.getName().startsWith("javax.")) {
return ObjectInputFilter.Status.REJECTED;
}
if (clazz == Person.class || clazz == Employee.class) {
return ObjectInputFilter.Status.ALLOWED;
}
}
return ObjectInputFilter.Status.REJECTED;
});
return in.readObject();
}
}
}
}
Best Practices
1. Always Declare serialVersionUID
// ✅ Good practice
public class MyClass implements Serializable {
private static final long serialVersionUID = 1L;
// class body
}
// ❌ Bad practice - auto-generated UID can cause issues
public class MyClass implements Serializable {
// No explicit serialVersionUID
}
2. Use transient for Sensitive Data
public class UserCredentials implements Serializable {
private String username;
private transient String password; // Won't be serialized
private transient String securityToken;
// Sensitive data should be transient
}
3. Implement Custom Serialization When Needed
public class ComplexObject implements Serializable {
private transient SomeNonSerializableComponent component;
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
// Custom serialization logic
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
// Custom deserialization logic
initializeTransientFields();
}
private void initializeTransientFields() {
// Reinitialize transient fields
this.component = new SomeNonSerializableComponent();
}
}
4. Handle Versioning Carefully
public class VersionedClass implements Serializable {
private static final long serialVersionUID = 2L; // Update when class changes
// Old fields
private String oldField;
// New fields should handle backward compatibility
private String newField;
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException {
in.defaultReadObject();
// Handle missing fields in older versions
if (newField == null) {
newField = "default-value";
}
}
}
Examples
Complete Working Example
import java.io.*;
import java.util.*;
public class SerializationCompleteExample {
public static void main(String[] args) {
// Example 1: Basic Serialization
basicSerializationExample();
// Example 2: Collection Serialization
collectionSerializationExample();
// Example 3: Custom Serialization
customSerializationExample();
// Example 4: Inheritance Serialization
inheritanceSerializationExample();
}
public static void basicSerializationExample() {
System.out.println("=== Basic Serialization Example ===");
Person person = new Person("John Doe", 30, "[email protected]");
// Serialize
String filename = "person.ser";
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(filename))) {
out.writeObject(person);
System.out.println("Serialized: " + person);
} catch (IOException e) {
e.printStackTrace();
}
// Deserialize
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(filename))) {
Person deserializedPerson = (Person) in.readObject();
System.out.println("Deserialized: " + deserializedPerson);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
public static void collectionSerializationExample() {
System.out.println("\n=== Collection Serialization Example ===");
List<Person> people = Arrays.asList(
new Person("Alice", 25, "[email protected]"),
new Person("Bob", 35, "[email protected]"),
new Person("Charlie", 28, "[email protected]")
);
// Serialize collection
String filename = "people.ser";
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(filename))) {
out.writeObject(people);
System.out.println("Serialized " + people.size() + " people");
} catch (IOException e) {
e.printStackTrace();
}
// Deserialize collection
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(filename))) {
@SuppressWarnings("unchecked")
List<Person> deserializedPeople = (List<Person>) in.readObject();
System.out.println("Deserialized " + deserializedPeople.size() + " people:");
deserializedPeople.forEach(System.out::println);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
public static void customSerializationExample() {
System.out.println("\n=== Custom Serialization Example ===");
BankAccount account = new BankAccount("123456789", "John Smith", 1500.75);
// Serialize with custom logic
String filename = "account.ser";
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(filename))) {
out.writeObject(account);
System.out.println("Serialized: " + account);
} catch (IOException e) {
e.printStackTrace();
}
// Deserialize with custom logic
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(filename))) {
BankAccount deserializedAccount = (BankAccount) in.readObject();
System.out.println("Deserialized: " + deserializedAccount);
System.out.println("Sensitive data: " + deserializedAccount.getSensitiveData());
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
public static void inheritanceSerializationExample() {
System.out.println("\n=== Inheritance Serialization Example ===");
Student student = new Student("Jane Doe", 20, "S12345", 3.8);
Professor professor = new Professor("Dr. Smith", 45, "Computer Science", 75000);
// Serialize both
String studentFile = "student.ser";
String professorFile = "professor.ser";
try (ObjectOutputStream out1 = new ObjectOutputStream(new FileOutputStream(studentFile));
ObjectOutputStream out2 = new ObjectOutputStream(new FileOutputStream(professorFile))) {
out1.writeObject(student);
out2.writeObject(professor);
System.out.println("Serialized student and professor");
} catch (IOException e) {
e.printStackTrace();
}
// Deserialize both
try (ObjectInputStream in1 = new ObjectInputStream(new FileInputStream(studentFile));
ObjectInputStream in2 = new ObjectInputStream(new FileInputStream(professorFile))) {
Student deserializedStudent = (Student) in1.readObject();
Professor deserializedProfessor = (Professor) in2.readObject();
System.out.println("Deserialized Student: " + deserializedStudent);
System.out.println("Deserialized Professor: " + deserializedProfessor);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
// Utility class for serialization operations
class SerializationUtils {
public static void serializeToFile(Object obj, String filename) throws IOException {
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(filename))) {
out.writeObject(obj);
}
}
public static Object deserializeFromFile(String filename)
throws IOException, ClassNotFoundException {
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(filename))) {
return in.readObject();
}
}
public static byte[] serializeToBytes(Object obj) throws IOException {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos)) {
oos.writeObject(obj);
return baos.toByteArray();
}
}
public static Object deserializeFromBytes(byte[] data)
throws IOException, ClassNotFoundException {
try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
ObjectInputStream ois = new ObjectInputStream(bais)) {
return ois.readObject();
}
}
}
Output:
=== Basic Serialization Example ===
Serialized: Person{name='John Doe', age=30, email='[email protected]'}
Deserialized: Person{name='John Doe', age=30, email='[email protected]'}
=== Collection Serialization Example ===
Serialized 3 people
Deserialized 3 people:
Person{name='Alice', age=25, email='[email protected]'}
Person{name='Bob', age=35, email='[email protected]'}
Person{name='Charlie', age=28, email='[email protected]'}
=== Custom Serialization Example ===
Serialized: BankAccount{accountNumber='123456789', accountHolder='John Smith', balance=1500.75}
Deserialized: BankAccount{accountNumber='123456789', accountHolder='John Smith', balance=1500.75}
Sensitive data: Confidential: 123456789
=== Inheritance Serialization Example ===
Serialized student and professor
Deserialized Student: Student{name='Jane Doe', age=20, studentId='S12345', gpa=0.0}
Deserialized Professor: Professor{name='Dr. Smith', age=45, department='Computer Science', salary=75000.0}
Important Notes
- Static fields are not serialized
- Transient fields are not serialized
- Parent class fields are serialized if parent implements Serializable
- Object graphs are serialized (references are followed)
- Circular references can cause issues
- Security: Be careful with deserializing untrusted data
- Performance: Serialization can be slow for large objects
- Versioning: Always consider backward compatibility
Serialization is a powerful feature in Java but should be used carefully considering security, performance, and maintainability aspects.