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.