CodeGym /Courses /JAVA 25 SELF /Introduction to object serialization: why you need it

Introduction to object serialization: why you need it

JAVA 25 SELF
Level 42 , Lesson 0
Available

1. Why you need serialization

Imagine your object is the stuff you take on vacation. Serialization is like packing everything in your suitcase into a special container that you can put in the baggage hold or mail. Deserialization, accordingly, is unpacking that container and getting your things back in their original form.

In essence, serialization turns an object into a stream of bytes that can be saved to a file, sent over the network, or just kept in memory. Deserialization does the reverse: it restores the object from that stream. Put simply, serialization is like “freezing” an object so you can later “defrost” it and get it back in the same state.

Saving object state between program runs

One of the most common scenarios is saving program state. For example, you have a list of users, game results, or application settings. It’s convenient to store all this directly as objects. To prevent data loss between runs, you serialize it to a file and deserialize it on the next launch.

A good example is a regular game save. When a player completes a level, their progress is “frozen” and written to a file via serialization. The next day they launch the game, and the progress is “defrosted”: the data from the file is turned back into objects, and the player continues from where they left off.

Let’s create a simple save:

import java.io.*;

// Player class must be Serializable
class Player implements Serializable {
    String name;
    int score;

    Player(String name, int score) {
        this.name = name;
        this.score = score;
    }
}

public class GameSaveExample {
    public static void main(String[] args) throws Exception {
        // Create a player object
        Player player = new Player("Ihor", 1500);

        // --- Saving (serialization) ---
        try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("save.dat"))) {
            out.writeObject(player);
            System.out.println("Progress saved!");
        }

        // --- Loading (deserialization) ---
        try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("save.dat"))) {
            Player loaded = (Player) in.readObject();
            System.out.println("Progress loaded: " + loaded.name + " with score " + loaded.score);
        }
    }
}

Note: for this code to work, the Player class must implement the Serializable interface. More about it — in the next lecture!

  • Player is a regular class with fields name and score, marked with the Serializable interface (implements Serializable).
  • ObjectOutputStream writes the object to the "save.dat" file.
  • ObjectInputStream reads this same object back.
  • As a result, we get a real save: on the next run, the program will load the player object with the same state.

Sending objects over the network and between JVMs

In distributed systems, it’s often necessary to send objects between different programs or even different machines. For example, you have a client and a server that need to exchange messages. Serialization lets you “pack” an object on one side, send it over the network, and “unpack” it on the other side.

Example: The client sends an order object (Order) to the server, the server receives it, deserializes it, and processes it.

Use in Java technologies

  • RMI (Remote Method Invocation): lets you invoke methods of remote objects — serialization is needed to transfer arguments and return values.
  • HTTP sessions: in servlets, session objects are serialized when the container restarts.
  • JMS (Java Message Service): messages between components can be serialized.
  • Caching: objects can be serialized for storage in a cache (to disk or to a distributed store).

Caching and portability

If you want to quickly save intermediate results (for example, for caching), serialization is an excellent tool. You serialize an object, save it to disk or memory, and then quickly restore it without recomputing.

2. Example use cases for serialization

Saving a collection of users to a file

Suppose you have a class User:

public class User {
    String name;
    int age;
    // ... other fields
}

And you have a list of users:

List<User> users = new ArrayList<>();
users.add(new User("John", 25));
users.add(new User("Alice", 30));
// ... and so on

To save this list to a file, you serialize it. When needed, you deserialize it and get the very same list with the same users. Remember that the User class (and all its fields) must support serialization, i.e., implement Serializable.

Sending a message between client and server

A classic example is a chat. The user writes a message, the Message object is serialized and sent over the network. The server receives the byte stream, deserializes the object, processes it, and possibly forwards it further.

import java.io.*;
import java.net.*;

// Message must be Serializable
class Message implements Serializable {
    String text;

    Message(String text) {
        this.text = text;
    }
}

// Server
class Server {
    public static void main(String[] args) throws Exception {
        try (ServerSocket serverSocket = new ServerSocket(5000)) {
            System.out.println("Server is waiting for a connection...");
            Socket socket = serverSocket.accept();
            System.out.println("Client connected!");

            try (ObjectInputStream in = new ObjectInputStream(socket.getInputStream())) {
                Message msg = (Message) in.readObject();
                System.out.println("Received message: " + msg.text);
            }
        }
    }
}

// Client
class Client {
    public static void main(String[] args) throws Exception {
        try (Socket socket = new Socket("localhost", 5000)) {
            try (ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream())) {
                Message msg = new Message("Hello, server!");
                out.writeObject(msg);
                System.out.println("Message sent!");
            }
        }
    }
}

How it works:

  1. Start Server first (it waits for a connection).
  2. Then start Client (it connects to "localhost:5000").
  3. The client serializes a Message object and sends it through the socket.
  4. The server receives the byte stream, deserializes it, and prints the text.

Here we use sockets (ServerSocket, Socket) — a networking mechanism you will study later. What matters now is not the networking details but the idea itself: the client creates a Message object, serializes it, and sends it; the server receives the byte stream, deserializes it back into an object, and prints the message. Thus, even if it’s not yet clear what ServerSocket and Socket are, the example shows the value of serialization: thanks to it, you can “pack” an object, send it over the network, and unpack it on the other side without extra conversions.

Caching objects

Large applications often use caching to speed things up. For example, results of complex computations are serialized and saved to a cache (a file, database, or distributed store). On the next request, the result can be quickly restored by deserializing the object.

import java.io.*;

// Computation result we want to cache
class Result implements Serializable {
    int value;

    Result(int value) {
        this.value = value;
    }
}

public class CacheExample {
    private static final String CACHE_FILE = "cache.dat";

    public static void main(String[] args) throws Exception {
        Result result;

        // Check if cache exists
        File file = new File(CACHE_FILE);
        if (file.exists()) {
            // Load result from cache
            try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(file))) {
                result = (Result) in.readObject();
                System.out.println("Loaded from cache: " + result.value);
            }
        } else {
            // "Heavy" computation (for example, just the square of a number)
            int x = 12345;
            System.out.println("Computing... (this is slow)");
            result = new Result(x * x);

            // Save result to cache
            try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file))) {
                out.writeObject(result);
                System.out.println("Saved to cache: " + result.value);
            }
        }
    }
}

3. Limitations and risks of serialization

Serialization is a powerful tool, but it has pitfalls. Let’s discuss the main limitations and risks.

Not all objects can be serialized

In Java, not all objects can be serialized “out of the box.” For example, objects tied to external resources (files, network connections, I/O streams) are not subject to serialization. This is logical: you can’t serialize an “open file” or a “live” network connection — their state depends on the operating system and runtime environment.

Example: A class with a field of type FileInputStream cannot be serialized — attempting to serialize it will result in an error.

Security concerns

Serialization is a potential security hole. If you deserialize data received from an untrusted source (for example, from the internet), an attacker can supply a malicious byte stream that leads to unexpected behavior in your program and sometimes even to execution of malicious code.

Rule: Never deserialize data from untrusted sources! It’s like accepting a package from an unknown sender — anything could be inside.

Version compatibility

If you change a class structure (for example, add or remove a field), objects serialized earlier may become incompatible with the new version of the class. This can lead to errors during deserialization. This topic will be covered in more detail in the following lectures.

Performance

Binary serialization in Java is fairly fast, but sometimes not the most compact and not always convenient for interoperability with other programming languages. For integration with external systems, text formats are often used (JSON, XML).

4. Common mistakes when first learning serialization

Mistake #1: trying to serialize an object that does not implement Serializable.
As a result, you will get a NotSerializableException. Don’t forget to explicitly specify implements Serializable in the class and ensure that all fields are also serializable!

Mistake #2: serializing objects with non-serializable fields.
If your class contains a field of a type that does not support serialization (for example, a stream or a DB connection), serialization will not work. The solution is to mark such fields as transient (more on this later).

Mistake #3: deserializing data from untrusted sources.
This can lead to security vulnerabilities or even execution of malicious code. Trust only the data that was serialized by your own program!

Mistake #4: changing the class structure after serialization.
If you saved an object and then added or removed a field in the class, attempting to deserialize may cause an error or produce “strange” values. More details — in the following lectures.

1
Task
JAVA 25 SELF, level 42, lesson 0
Locked
Compressing a string into a ZIP archive
Compressing a string into a ZIP archive
1
Task
JAVA 25 SELF, level 42, lesson 0
Locked
Extracting a File from a ZIP Archive
Extracting a File from a ZIP Archive
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION