FileOutputStream, ObjectOutputStream, FileInputStream, and ObjectInputStream in Java

Serialization and deserialization in Java are essential techniques for saving and retrieving objects from storage mediums such as files or transmitting them over networks. This article explores FileOutputStream, ObjectOutputStream, FileInputStream, and ObjectInputStream, along with implementing writeObject() and readObject() methods to handle non-serializable fields.

FileOutputStream and ObjectOutputStream

FileOutputStream

FileOutputStream is used to write raw bytes to a file. It is commonly used when dealing with binary data such as images, audio, or serialized Java objects.

import java.io.FileOutputStream;
import java.io.IOException;

public class FileOutputStreamExample {
    public static void main(String[] args) {
        try (FileOutputStream fos = new FileOutputStream("output.txt")) {
            String data = "Hello, FileOutputStream!";
            fos.write(data.getBytes());
            System.out.println("Data written successfully.");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

ObjectOutputStream

ObjectOutputStream allows writing Java objects to an output stream. It serializes objects into a format that can be stored in a file or transmitted over a network.

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

class Person implements java.io.Serializable {
    private static final long serialVersionUID = 1L;
    String name;
    int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

public class ObjectOutputStreamExample {
    public static void main(String[] args) {
        try (FileOutputStream fos = new FileOutputStream("person.ser");
             ObjectOutputStream oos = new ObjectOutputStream(fos)) {

            Person person = new Person("John Doe", 30);
            oos.writeObject(person);
            System.out.println("Object serialized successfully.");

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

FileInputStream and ObjectInputStream

FileInputStream

FileInputStream is used to read raw bytes from a file. It is commonly used for reading binary data.

import java.io.FileInputStream;
import java.io.IOException;

public class FileInputStreamExample {
    public static void main(String[] args) {
        try (FileInputStream fis = new FileInputStream("output.txt")) {
            int content;
            while ((content = fis.read()) != -1) {
                System.out.print((char) content);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

ObjectInputStream

ObjectInputStream is used to read objects that have been serialized and stored in a file.

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class ObjectInputStreamExample {
    public static void main(String[] args) {
        try (FileInputStream fis = new FileInputStream("person.ser");
             ObjectInputStream ois = new ObjectInputStream(fis)) {

            Person person = (Person) ois.readObject();
            System.out.println("Object deserialized: " + person.name + ", " + person.age);

        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

Custom Serialization with writeObject() and readObject()

Sometimes, objects contain non-serializable fields (e.g., transient fields, file handles, database connections) that should not or cannot be serialized directly. Java allows customization of serialization by overriding writeObject() and readObject().

Handling Non-Serializable Fields

import java.io.*;

class SecureData implements Serializable {
    private static final long serialVersionUID = 1L;
    private String username;
    private transient String password; // This field won't be serialized

    public SecureData(String username, String password) {
        this.username = username;
        this.password = password;
    }

    private void writeObject(ObjectOutputStream oos) throws IOException {
        oos.defaultWriteObject();
        oos.writeObject(encrypt(password)); // Encrypt before serialization
    }

    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        ois.defaultReadObject();
        password = decrypt((String) ois.readObject()); // Decrypt after deserialization
    }

    private String encrypt(String data) {
        return new StringBuilder(data).reverse().toString(); // Simple encryption
    }

    private String decrypt(String data) {
        return new StringBuilder(data).reverse().toString(); // Simple decryption
    }

    @Override
    public String toString() {
        return "Username: " + username + ", Password: " + password;
    }
}

public class CustomSerializationExample {
    public static void main(String[] args) {
        SecureData secureData = new SecureData("admin", "secret123");

        // Serialization
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("securedata.ser"))) {
            oos.writeObject(secureData);
            System.out.println("SecureData serialized.");
        } catch (IOException e) {
            e.printStackTrace();
        }

        // Deserialization
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("securedata.ser"))) {
            SecureData deserializedData = (SecureData) ois.readObject();
            System.out.println("Deserialized: " + deserializedData);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

Explanation for above code:

  • The password field is marked as transient, meaning it won't be serialized.

  • The writeObject() method encrypts the password before serialization.

  • The readObject() method decrypts it after deserialization.

  • This ensures that sensitive information is protected while still being stored.


Conclusion

In this article, we covered the essential Java classes for file handling (FileOutputStream and FileInputStream) and object serialization (ObjectOutputStream and ObjectInputStream). We also demonstrated how to customize serialization and deserialization to handle non-serializable fields securely. These techniques are crucial when dealing with sensitive data or complex object structures in Java applications.

0
Subscribe to my newsletter

Read articles from Ali Rıza Şahin directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Ali Rıza Şahin
Ali Rıza Şahin

Product-oriented Software Engineer with a solid understanding of web programming fundamentals and software development methodologies such as agile and scrum.