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”).
GO TO FULL VERSION