CodeGym /Courses /JAVA 25 SELF /Review of common mistakes in collection serialization

Review of common mistakes in collection serialization

JAVA 25 SELF
Level 44, Lesson 4
Available

1. NotSerializableException: when a collection refuses to serialize

The most common and most insidious error when serializing collections is java.io.NotSerializableException. It occurs if at least one element of the collection does not implement the Serializable interface.

Let’s look at a naive example:

import java.io.*;
import java.util.*;

class Book {
    String title;
    Book(String title) { this.title = title; }
}

public class LibraryApp {
    public static void main(String[] args) throws Exception {
        List<Book> books = new ArrayList<>();
        books.add(new Book("Dombey and Son"));

        // Attempt to serialize the collection
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("books.ser"))) {
            oos.writeObject(books); // Boom! NotSerializableException
        }
    }
}

What will happen? At oos.writeObject(books) you will get an exception:

java.io.NotSerializableException: Book

Why? Because the Book class does not implement the Serializable interface. Even if the collection itself (ArrayList) can be serialized, the elements of the collection must be serializable!

How to diagnose

The error always contains the class that caused the problem — look for it in the exception message. If the collection is large and the error occurs only under certain conditions, it’s possible some element was added by accident and does not implement Serializable.

How to fix

Add implements Serializable to your class:

class Book implements Serializable {
    String title;
    Book(String title) { this.title = title; }
}

Tip: If the collection contains different types of objects, check each of them for Serializable conformance!

2. ClassCastException during deserialization: when generics go wrong

In Java, information about collection generic parameters is erased after compilation (type erasure). This means that if you serialized a List<String> and deserialize it as a List<Integer>, the compiler won’t notice the error, but at runtime you will get a ClassCastException.

Example:

// Serialization
List<String> names = Arrays.asList("Anna", "Boris");
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("names.ser"))) {
    oos.writeObject(names);
}

// Deserialization (DANGEROUS!)
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("names.ser"))) {
    List<Integer> numbers = (List<Integer>) ois.readObject(); // unchecked cast
    Integer first = numbers.get(0); // Boom! ClassCastException
}

Error:

java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.Integer

How to avoid

  • Do not use “raw” collections (raw types) and do not cast types unless necessary.
  • Check element types after deserialization if you are unsure about the contents.
  • Document which collection type is serialized and expected at read time.

Example of safe deserialization:

Object obj = ois.readObject();
if (obj instanceof List<?>) {
    List<?> list = (List<?>) obj;
    if (!list.isEmpty() && list.get(0) instanceof String) {
        @SuppressWarnings("unchecked")
        List<String> safeNames = (List<String>) obj; // warning suppressed, but the type is checked!
    }
}

3. Changing class structure: serialVersionUID and backward compatibility

You serialized a collection and then decided to add a new field to the element class, change a field name, or even change the class structure entirely. Now, when you try to deserialize the old file, you get a mysterious error:

java.io.InvalidClassException: Book; local class incompatible: stream classdesc serialVersionUID = 1234, local class serialVersionUID = 5678

Why this happens

Every serializable class gets a unique version identifier — serialVersionUID. If the class changes (for example, you added a field), the JVM calculates a new serialVersionUID, and deserialization detects that the class version does not match the one used during serialization.

How to avoid

  • Declare serialVersionUID explicitly in your classes:
class Book implements Serializable {
    private static final long serialVersionUID = 1L;
    String title;
    // ...
}
  • Preserve backward compatibility: do not remove or rename fields if you plan to read old files.
  • Test deserialization after changes.

What if you still need to change the class?

  • Consider implementing readObject/writeObject for manual control over serialization.
  • Or migrate the data: read the old file with the old version of the class, then resave it in the new format.

4. Data loss when serializing immutable collections

Newer versions of Java introduced immutable collections, for example, created via List.of(), Set.of(), Map.of(). In older versions of Java (before 12) and some third-party implementations, serialization of such collections may not work correctly: after deserialization the collection becomes a regular mutable one or an error occurs.

Example:

List<String> list = List.of("a", "b", "c");
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("list.ser"))) {
    oos.writeObject(list);
}

On older JVMs, an error occurred during deserialization, or the collection stopped being immutable.

How to avoid

  • Check the documentation for the Java version you use.
  • Test serialization and deserialization of such collections.
  • If you need to preserve immutability, wrap the collection after deserialization using Collections.unmodifiableList(list).

5. Serializing transient and static fields

What happens to such fields:

  • transient — fields marked with this keyword are not serialized at all. After deserialization, they will have the default value (for example, null or 0).
  • static — class fields (not object fields) are never serialized.

Example:

class Book implements Serializable {
    String title;
    transient String cache; // not serialized!
    static String publisher = "Default"; // also not serialized!
}

Why this matters

If you store some computed values or a cache inside an object, mark them as transient — this saves space and speeds up serialization.

Caution: After deserialization, transient fields need to be recomputed or initialized again.

6. Serializing large collections: performance and file size

Problems:

  • Large collections (for example, a million objects) can result in huge files, long write and read times, and sometimes even out-of-memory conditions (OutOfMemoryError).
  • When serializing an object graph (for example, complex interrelated collections), the file size may grow unexpectedly.

How to avoid

  • Serialize the collection in parts: for example, write objects one by one or in small batches.
  • Use streaming processing: instead of serializing the entire collection at once, serialize elements as needed.
  • Compress files: use GZIPOutputStream to reduce file size.

Example of streaming serialization:

try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("books.ser"))) {
    for (Book book : bigList) {
        oos.writeObject(book);
    }
}

Note: With this approach, deserialization requires knowing how many objects were written (or using a special “end marker”).

1
Task
JAVA 25 SELF, level 44, lesson 4
Locked
Incorrect data interpretation: `ClassCastException` after deserialization 🚨
Incorrect data interpretation: `ClassCastException` after deserialization 🚨
1
Task
JAVA 25 SELF, level 44, lesson 4
Locked
Digital Archivist: Efficient Storage of Large Data Sets with Compression 🗄️
Digital Archivist: Efficient Storage of Large Data Sets with Compression 🗄️
1
Survey/quiz
Serializing complex structures, level 44, lesson 4
Unavailable
Serializing complex structures
Serializing complex structures
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION