ObjectInputStream and ObjectOutputStream in Java

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:

  1. Serialization: Convert objects to byte stream using ObjectOutputStream
  2. Deserialization: Reconstruct objects from byte stream using ObjectInputStream
  3. Serializable Interface: Must be implemented by classes to enable serialization
  4. transient keyword: Excludes fields from serialization
  5. serialVersionUID: Version control for serialized classes

Best Practices:

  1. Always declare serialVersionUID to maintain version compatibility
  2. Use transient for sensitive data or fields that shouldn't be persisted
  3. Implement custom serialization when needed using writeObject() and readObject()
  4. Validate objects during deserialization
  5. Handle serialization errors gracefully
  6. Consider using Serialization Proxy Pattern for complex validation
  7. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper