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