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.