These classes are used for object serialization - converting objects into a byte stream for storage or transmission, and reconstructing objects from that byte stream.
1. Understanding Serialization
What is Serialization?
- Serialization: Converting an object into a byte stream
- Deserialization: Reconstructing an object from a byte stream
- Uses: Network transmission, persistence, deep copying
Serializable Interface
import java.io.Serializable;
// A class must implement Serializable to be serialized
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
private transient String password; // transient fields are not serialized
public Person(String name, int age, String password) {
this.name = name;
this.age = age;
this.password = password;
}
// 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 getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age +
", password='" + password + "'}";
}
}
2. Basic ObjectOutputStream Usage
Writing Objects to File
import java.io.*;
import java.util.ArrayList;
import java.util.List;
public class ObjectOutputStreamExample {
public static void main(String[] args) {
// Create some Person objects
Person person1 = new Person("John Doe", 30, "secret123");
Person person2 = new Person("Jane Smith", 25, "password456");
Person person3 = new Person("Bob Johnson", 35, "admin789");
// Write single object to file
try (ObjectOutputStream oos =
new ObjectOutputStream(new FileOutputStream("person.ser"))) {
oos.writeObject(person1);
System.out.println("Person object written to file successfully!");
} catch (IOException e) {
System.out.println("Error writing object: " + e.getMessage());
}
// Write multiple objects to file
writeMultipleObjects();
// Write collection of objects
writeCollection();
}
public static void writeMultipleObjects() {
List<Person> people = new ArrayList<>();
people.add(new Person("Alice Brown", 28, "alice123"));
people.add(new Person("Charlie Wilson", 42, "charlie456"));
people.add(new Person("Diana Lee", 31, "diana789"));
try (ObjectOutputStream oos =
new ObjectOutputStream(new FileOutputStream("people.ser"))) {
// Write the entire list as a single object
oos.writeObject(people);
System.out.println("List of people written to file successfully!");
} catch (IOException e) {
System.out.println("Error writing objects: " + e.getMessage());
}
}
public static void writeCollection() {
try (ObjectOutputStream oos =
new ObjectOutputStream(new FileOutputStream("collection.ser"))) {
// Write different types of objects
oos.writeObject("This is a string");
oos.writeObject(42);
oos.writeObject(new ArrayList<>(List.of("A", "B", "C")));
oos.writeObject(new Person("Test User", 99, "testpass"));
System.out.println("Multiple object types written successfully!");
} catch (IOException e) {
System.out.println("Error writing collection: " + e.getMessage());
}
}
}
3. Basic ObjectInputStream Usage
Reading Objects from File
import java.io.*;
import java.util.List;
public class ObjectInputStreamExample {
public static void main(String[] args) {
// Read single object
readSingleObject();
// Read multiple objects
readMultipleObjects();
// Read collection
readCollection();
}
public static void readSingleObject() {
try (ObjectInputStream ois =
new ObjectInputStream(new FileInputStream("person.ser"))) {
Person person = (Person) ois.readObject();
System.out.println("Read person: " + person);
} catch (IOException | ClassNotFoundException e) {
System.out.println("Error reading object: " + e.getMessage());
}
}
public static void readMultipleObjects() {
try (ObjectInputStream ois =
new ObjectInputStream(new FileInputStream("people.ser"))) {
@SuppressWarnings("unchecked")
List<Person> people = (List<Person>) ois.readObject();
System.out.println("Read " + people.size() + " people:");
for (Person person : people) {
System.out.println(" - " + person);
}
} catch (IOException | ClassNotFoundException e) {
System.out.println("Error reading objects: " + e.getMessage());
}
}
public static void readCollection() {
try (ObjectInputStream ois =
new ObjectInputStream(new FileInputStream("collection.ser"))) {
// Read objects in the same order they were written
String str = (String) ois.readObject();
Integer number = (Integer) ois.readObject();
@SuppressWarnings("unchecked")
List<String> list = (List<String>) ois.readObject();
Person person = (Person) ois.readObject();
System.out.println("String: " + str);
System.out.println("Number: " + number);
System.out.println("List: " + list);
System.out.println("Person: " + person);
} catch (IOException | ClassNotFoundException e) {
System.out.println("Error reading collection: " + e.getMessage());
}
}
}
4. Complete Serialization Example
Complex Object with Serialization
import java.io.Serializable;
import java.util.*;
class Address implements Serializable {
private static final long serialVersionUID = 1L;
private String street;
private String city;
private String zipCode;
public Address(String street, String city, String zipCode) {
this.street = street;
this.city = city;
this.zipCode = zipCode;
}
// Getters and toString
public String getStreet() { return street; }
public String getCity() { return city; }
public String getZipCode() { return zipCode; }
@Override
public String toString() {
return street + ", " + city + " " + zipCode;
}
}
class Employee implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int employeeId;
private transient double salary; // Not serialized
private Address address;
private List<String> skills;
private Map<String, String> contacts;
public Employee(String name, int employeeId, double salary, Address address) {
this.name = name;
this.employeeId = employeeId;
this.salary = salary;
this.address = address;
this.skills = new ArrayList<>();
this.contacts = new HashMap<>();
}
public void addSkill(String skill) {
skills.add(skill);
}
public void addContact(String type, String value) {
contacts.put(type, value);
}
// Getters and setters
public String getName() { return name; }
public int getEmployeeId() { return employeeId; }
public double getSalary() { return salary; }
public Address getAddress() { return address; }
public List<String> getSkills() { return skills; }
public Map<String, String> getContacts() { return contacts; }
public void setSalary(double salary) { this.salary = salary; }
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", employeeId=" + employeeId +
", salary=" + salary +
", address=" + address +
", skills=" + skills +
", contacts=" + contacts +
'}';
}
}
public class CompleteSerializationExample {
public static void serializeEmployee(Employee emp, String filename) {
try (ObjectOutputStream oos =
new ObjectOutputStream(new FileOutputStream(filename))) {
oos.writeObject(emp);
System.out.println("Employee serialized successfully: " + emp.getName());
} catch (IOException e) {
System.out.println("Error serializing employee: " + e.getMessage());
}
}
public static Employee deserializeEmployee(String filename) {
try (ObjectInputStream ois =
new ObjectInputStream(new FileInputStream(filename))) {
Employee emp = (Employee) ois.readObject();
System.out.println("Employee deserialized successfully: " + emp.getName());
return emp;
} catch (IOException | ClassNotFoundException e) {
System.out.println("Error deserializing employee: " + e.getMessage());
return null;
}
}
public static void main(String[] args) {
// Create employee with complex data
Address address = new Address("123 Main St", "New York", "10001");
Employee employee = new Employee("John Manager", 1001, 75000.0, address);
employee.addSkill("Java");
employee.addSkill("Spring Boot");
employee.addSkill("Database Design");
employee.addContact("email", "[email protected]");
employee.addContact("phone", "+1-555-0123");
employee.addContact("linkedin", "linkedin.com/in/johnmanager");
// Serialize the employee
serializeEmployee(employee, "employee.ser");
// Deserialize the employee
Employee deserializedEmp = deserializeEmployee("employee.ser");
if (deserializedEmp != null) {
System.out.println("\nDeserialized Employee Details:");
System.out.println("Name: " + deserializedEmp.getName());
System.out.println("ID: " + deserializedEmp.getEmployeeId());
System.out.println("Salary: " + deserializedEmp.getSalary()); // Will be 0.0 (transient)
System.out.println("Address: " + deserializedEmp.getAddress());
System.out.println("Skills: " + deserializedEmp.getSkills());
System.out.println("Contacts: " + deserializedEmp.getContacts());
}
}
}
5. Custom Serialization Methods
Using writeObject() and readObject() Methods
import java.io.*;
class BankAccount implements Serializable {
private static final long serialVersionUID = 1L;
private String accountNumber;
private transient double balance; // Don't serialize directly
private String accountHolder;
private transient String encryptedPassword;
public BankAccount(String accountNumber, double balance, String accountHolder, String password) {
this.accountNumber = accountNumber;
this.balance = balance;
this.accountHolder = accountHolder;
this.encryptedPassword = encrypt(password);
}
private String encrypt(String password) {
// Simple encryption for demonstration
return new StringBuilder(password).reverse().toString();
}
private String decrypt(String encrypted) {
return new StringBuilder(encrypted).reverse().toString();
}
// Custom serialization logic
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject(); // Serialize non-transient fields normally
// Custom serialization for transient fields
oos.writeDouble(balance); // Serialize balance manually
oos.writeObject(encryptedPassword); // Serialize encrypted password
System.out.println("Custom writeObject called for: " + accountNumber);
}
// Custom deserialization logic
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject(); // Deserialize non-transient fields normally
// Custom deserialization for transient fields
this.balance = ois.readDouble();
this.encryptedPassword = (String) ois.readObject();
System.out.println("Custom readObject called for: " + accountNumber);
}
// Getters and business methods
public String getAccountNumber() { return accountNumber; }
public double getBalance() { return balance; }
public String getAccountHolder() { return accountHolder; }
public boolean validatePassword(String password) {
return encrypt(password).equals(encryptedPassword);
}
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
public boolean withdraw(double amount) {
if (amount > 0 && balance >= amount) {
balance -= amount;
return true;
}
return false;
}
@Override
public String toString() {
return "BankAccount{" +
"accountNumber='" + accountNumber + '\'' +
", balance=" + balance +
", accountHolder='" + accountHolder + '\'' +
'}';
}
}
public class CustomSerializationExample {
public static void main(String[] args) {
BankAccount account = new BankAccount("123456789", 5000.0, "John Doe", "securePass123");
// Serialize with custom logic
try (ObjectOutputStream oos =
new ObjectOutputStream(new FileOutputStream("bankaccount.ser"))) {
oos.writeObject(account);
System.out.println("Bank account serialized with custom logic");
} catch (IOException e) {
System.out.println("Error: " + e.getMessage());
}
// Deserialize with custom logic
try (ObjectInputStream ois =
new ObjectInputStream(new FileInputStream("bankaccount.ser"))) {
BankAccount deserializedAccount = (BankAccount) ois.readObject();
System.out.println("Bank account deserialized: " + deserializedAccount);
// Test password validation
System.out.println("Password validation: " +
deserializedAccount.validatePassword("securePass123"));
// Test transactions
deserializedAccount.deposit(1000);
deserializedAccount.withdraw(500);
System.out.println("After transactions: " + deserializedAccount);
} catch (IOException | ClassNotFoundException e) {
System.out.println("Error: " + e.getMessage());
}
}
}
6. Serialization with Inheritance
import java.io.*;
class Vehicle implements Serializable {
private static final long serialVersionUID = 1L;
protected String brand;
protected int year;
protected transient String registrationNumber; // Not serialized
public Vehicle(String brand, int year, String registrationNumber) {
this.brand = brand;
this.year = year;
this.registrationNumber = registrationNumber;
}
// Custom serialization
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject();
oos.writeObject(registrationNumber); // Manually serialize transient field
}
// Custom deserialization
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject();
this.registrationNumber = (String) ois.readObject();
}
@Override
public String toString() {
return "Vehicle{brand='" + brand + "', year=" + year +
", registration='" + registrationNumber + "'}";
}
}
class Car extends Vehicle {
private static final long serialVersionUID = 1L;
private int doors;
private String fuelType;
private transient double engineSize; // Not serialized
public Car(String brand, int year, String registrationNumber,
int doors, String fuelType, double engineSize) {
super(brand, year, registrationNumber);
this.doors = doors;
this.fuelType = fuelType;
this.engineSize = engineSize;
}
// Custom serialization for subclass
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject(); // Serialize Car fields
oos.writeDouble(engineSize); // Manually serialize transient field
}
// Custom deserialization for subclass
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject(); // Deserialize Car fields
this.engineSize = ois.readDouble(); // Manually deserialize transient field
}
@Override
public String toString() {
return "Car{" +
"brand='" + brand + '\'' +
", year=" + year +
", registration='" + registrationNumber + '\'' +
", doors=" + doors +
", fuelType='" + fuelType + '\'' +
", engineSize=" + engineSize +
'}';
}
}
public class InheritanceSerialization {
public static void main(String[] args) {
Car car = new Car("Toyota", 2022, "ABC123", 4, "Gasoline", 2.5);
// Serialize
try (ObjectOutputStream oos =
new ObjectOutputStream(new FileOutputStream("car.ser"))) {
oos.writeObject(car);
System.out.println("Car serialized: " + car);
} catch (IOException e) {
System.out.println("Error serializing: " + e.getMessage());
}
// Deserialize
try (ObjectInputStream ois =
new ObjectInputStream(new FileInputStream("car.ser"))) {
Car deserializedCar = (Car) ois.readObject();
System.out.println("Car deserialized: " + deserializedCar);
} catch (IOException | ClassNotFoundException e) {
System.out.println("Error deserializing: " + e.getMessage());
}
}
}
7. Handling Serialization Errors
Serialization with Error Handling
import java.io.*;
import java.util.*;
class Product implements Serializable {
private static final long serialVersionUID = 1L;
private String id;
private String name;
private double price;
private transient Date createdDate; // Will be null after deserialization
public Product(String id, String name, double price) {
this.id = id;
this.name = name;
this.price = price;
this.createdDate = new Date(); // Current date/time
}
// Custom serialization to handle potential issues
private void writeObject(ObjectOutputStream oos) throws IOException {
try {
oos.defaultWriteObject();
// We could serialize the date if needed
oos.writeLong(createdDate != null ? createdDate.getTime() : -1);
} catch (Exception e) {
System.err.println("Error during serialization: " + e.getMessage());
throw new IOException("Serialization failed for product: " + id, e);
}
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
try {
ois.defaultReadObject();
long time = ois.readLong();
this.createdDate = time != -1 ? new Date(time) : null;
} catch (Exception e) {
System.err.println("Error during deserialization: " + e.getMessage());
// Set default values or handle gracefully
this.createdDate = new Date(); // Set to current date
}
}
@Override
public String toString() {
return "Product{id='" + id + "', name='" + name +
"', price=" + price + ", created=" + createdDate + "}";
}
}
public class SerializationErrorHandling {
public static void serializeWithValidation(String filename, Object obj) {
// Validate before serialization
if (obj == null) {
throw new IllegalArgumentException("Cannot serialize null object");
}
if (!(obj instanceof Serializable)) {
throw new IllegalArgumentException("Object must implement Serializable");
}
File file = new File(filename);
File tempFile = new File(filename + ".tmp");
try {
// Write to temporary file first
try (ObjectOutputStream oos =
new ObjectOutputStream(new FileOutputStream(tempFile))) {
oos.writeObject(obj);
}
// If successful, rename to actual file
if (file.exists()) {
file.delete();
}
tempFile.renameTo(file);
System.out.println("Serialization successful: " + filename);
} catch (IOException e) {
// Clean up temporary file on error
if (tempFile.exists()) {
tempFile.delete();
}
System.err.println("Serialization failed: " + e.getMessage());
}
}
public static Object deserializeWithValidation(String filename) {
File file = new File(filename);
if (!file.exists()) {
System.err.println("File not found: " + filename);
return null;
}
if (file.length() == 0) {
System.err.println("File is empty: " + filename);
return null;
}
try (ObjectInputStream ois =
new ObjectInputStream(new FileInputStream(file))) {
return ois.readObject();
} catch (IOException | ClassNotFoundException e) {
System.err.println("Deserialization failed: " + e.getMessage());
return null;
}
}
public static void main(String[] args) {
Product product = new Product("P1001", "Laptop", 999.99);
// Serialize with error handling
serializeWithValidation("product.ser", product);
// Deserialize with error handling
Product deserialized = (Product) deserializeWithValidation("product.ser");
if (deserialized != null) {
System.out.println("Deserialized product: " + deserialized);
}
// Test with invalid file
Object invalid = deserializeWithValidation("nonexistent.ser");
System.out.println("Result for invalid file: " + invalid);
}
}
8. Advanced Usage - Serialization Proxy Pattern
import java.io.*;
import java.util.InvalidPropertiesFormatException;
class Student implements Serializable {
private static final long serialVersionUID = 1L;
private final String name;
private final int age;
private final String studentId;
public Student(String name, int age, String studentId) {
// Validate input
if (name == null || name.trim().isEmpty()) {
throw new IllegalArgumentException("Name cannot be null or empty");
}
if (age < 16 || age > 100) {
throw new IllegalArgumentException("Age must be between 16 and 100");
}
if (studentId == null || !studentId.matches("S\\d{6}")) {
throw new IllegalArgumentException("Student ID must match pattern SXXXXXX");
}
this.name = name;
this.age = age;
this.studentId = studentId;
}
// Serialization proxy class
private static class StudentProxy implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
private String studentId;
StudentProxy(Student student) {
this.name = student.name;
this.age = student.age;
this.studentId = student.studentId;
}
private Object readResolve() {
// Validate during deserialization
try {
return new Student(name, age, studentId);
} catch (IllegalArgumentException e) {
throw new InvalidObjectException("Invalid student data: " + e.getMessage());
}
}
}
// Replace with proxy during serialization
private Object writeReplace() {
return new StudentProxy(this);
}
// Prevent direct deserialization
private void readObject(ObjectInputStream ois) throws InvalidObjectException {
throw new InvalidObjectException("Proxy required");
}
// Getters
public String getName() { return name; }
public int getAge() { return age; }
public String getStudentId() { return studentId; }
@Override
public String toString() {
return "Student{name='" + name + "', age=" + age + ", studentId='" + studentId + "'}";
}
}
public class SerializationProxyPattern {
public static void main(String[] args) {
Student student = new Student("Alice Johnson", 20, "S123456");
// Serialize
try (ObjectOutputStream oos =
new ObjectOutputStream(new FileOutputStream("student.ser"))) {
oos.writeObject(student);
System.out.println("Student serialized: " + student);
} catch (IOException e) {
System.out.println("Error: " + e.getMessage());
}
// Deserialize
try (ObjectInputStream ois =
new ObjectInputStream(new FileInputStream("student.ser"))) {
Student deserializedStudent = (Student) ois.readObject();
System.out.println("Student deserialized: " + deserializedStudent);
} catch (IOException | ClassNotFoundException e) {
System.out.println("Error: " + e.getMessage());
}
// Try to create invalid student (will fail during deserialization)
try {
// This would normally fail at construction, but let's test with proxy
Student invalid = new Student("", 15, "invalid");
} catch (IllegalArgumentException e) {
System.out.println("Correctly caught invalid student: " + e.getMessage());
}
}
}
9. Performance and Best Practices
Performance Comparison
import java.io.*;
import java.util.ArrayList;
import java.util.List;
public class SerializationPerformance {
public static void main(String[] args) {
int objectCount = 10000;
List<Person> people = generatePeople(objectCount);
System.out.println("Serialization Performance Test (" + objectCount + " objects)");
System.out.println("=========================================");
// Test ObjectOutputStream
long start = System.currentTimeMillis();
try (ObjectOutputStream oos =
new ObjectOutputStream(new FileOutputStream("test.ser"))) {
oos.writeObject(people);
} catch (IOException e) {
e.printStackTrace();
}
long serializationTime = System.currentTimeMillis() - start;
// Test ObjectInputStream
start = System.currentTimeMillis();
try (ObjectInputStream ois =
new ObjectInputStream(new FileInputStream("test.ser"))) {
@SuppressWarnings("unchecked")
List<Person> deserialized = (List<Person>) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
long deserializationTime = System.currentTimeMillis() - start;
System.out.printf("Serialization time: %d ms%n", serializationTime);
System.out.printf("Deserialization time: %d ms%n", deserializationTime);
// Cleanup
new File("test.ser").delete();
}
private static List<Person> generatePeople(int count) {
List<Person> people = new ArrayList<>();
for (int i = 0; i < count; i++) {
people.add(new Person("Person" + i, 20 + (i % 50), "pass" + i));
}
return people;
}
}
Summary
Key Points:
- Serialization: Convert objects to byte stream using
ObjectOutputStream - Deserialization: Reconstruct objects from byte stream using
ObjectInputStream - Serializable Interface: Must be implemented by classes to enable serialization
- transient keyword: Excludes fields from serialization
- serialVersionUID: Version control for serialized classes
Best Practices:
- Always declare serialVersionUID to maintain version compatibility
- Use transient for sensitive data or fields that shouldn't be persisted
- Implement custom serialization when needed using
writeObject()andreadObject() - Validate objects during deserialization
- Handle serialization errors gracefully
- Consider using Serialization Proxy Pattern for complex validation
- Be cautious with object graphs to avoid circular references
Common Use Cases:
- Persistence: Saving application state
- Network communication: Sending objects over network
- Caching: Storing computed results
- Deep copying: Creating complete object copies
- Distributed computing: Sharing objects between JVMs
Object serialization is a powerful feature but should be used carefully, especially considering security implications and version compatibility.