1. NotSerializableException: quando a coleção não quer ser serializada
O erro mais comum e mais traiçoeiro na serialização de coleções — é java.io.NotSerializableException. Ele ocorre se pelo menos um elemento da coleção não implementa a interface Serializable.
Vamos ver um exemplo ingênuo:
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 e Filho"));
// Tentativa de serializar a coleção
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("books.ser"))) {
oos.writeObject(books); // BUM! NotSerializableException
}
}
}
O que vai acontecer? Na etapa oos.writeObject(books) você receberá a exceção:
java.io.NotSerializableException: Book
Por quê? Porque a classe Book não implementa a interface Serializable. Mesmo que a própria coleção (ArrayList) saiba ser serializada, os elementos da coleção precisam ser serializáveis!
Como diagnosticar
Na mensagem de erro sempre está indicada a classe que causou o problema — procure-a na mensagem da exceção. Se a coleção for grande e o erro ocorrer apenas em determinadas condições, é possível que algum elemento tenha sido adicionado por engano e não implemente Serializable.
Como corrigir
Adicione implements Serializable à sua classe:
class Book implements Serializable {
String title;
Book(String title) { this.title = title; }
}
Dica: Se a coleção contiver diferentes tipos de objetos, verifique todos eles quanto à conformidade com Serializable!
2. ClassCastException na desserialização: quando os genéricos pregam peças
Em Java, a informação sobre os parâmetros genéricos das coleções é apagada após a compilação (type erasure). Isso significa que, se você serializou um List<String>, mas desserializa como List<Integer>, o compilador não perceberá o erro, porém em tempo de execução você terá um ClassCastException.
Exemplo:
// Serialização
List<String> names = Arrays.asList("Anna", "Boris");
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("names.ser"))) {
oos.writeObject(names);
}
// Desserialização (PERIGOSO!)
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("names.ser"))) {
List<Integer> numbers = (List<Integer>) ois.readObject(); // unchecked cast
Integer first = numbers.get(0); // BUM! ClassCastException
}
Erro:
java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.Integer
Como evitar
- Não use coleções de tipo bruto (raw types) e não faça casts sem necessidade.
- Verifique os tipos dos elementos após a desserialização, se não tiver certeza do conteúdo.
- Documente qual tipo de coleção é serializado e o que é esperado na leitura.
Exemplo de desserialização segura:
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 suprimido, mas o tipo foi verificado!
}
}
3. Alteração da estrutura de classes: serialVersionUID e compatibilidade com versões anteriores
Você serializou uma coleção e depois decidiu adicionar um novo campo na classe do elemento, mudar o nome de um campo ou até alterar a estrutura da classe. Agora, ao tentar desserializar o arquivo antigo, você obterá um erro enigmático:
java.io.InvalidClassException: Book; local class incompatible: stream classdesc serialVersionUID = 1234, local class serialVersionUID = 5678
Por que isso acontece
Cada classe serializável recebe um identificador de versão exclusivo — serialVersionUID. Se a classe mudou (por exemplo, você adicionou um campo), a JVM calcula um novo serialVersionUID, e a desserialização percebe que a versão da classe não coincide com a que existia na serialização.
Como evitar
- Declare explicitamente o serialVersionUID em suas classes:
class Book implements Serializable {
private static final long serialVersionUID = 1L;
String title;
// ...
}
- Mantenha compatibilidade com versões anteriores: não remova nem renomeie campos se planeja ler arquivos antigos.
- Teste a desserialização após alterações.
O que fazer se você realmente precisar alterar a classe?
- Considere implementar os métodos readObject/writeObject para controlar a serialização manualmente.
- Ou migre os dados: leia o arquivo antigo com a versão antiga da classe e depois salve novamente no novo formato.
4. Perda de dados na serialização de coleções imutáveis
Nas versões mais recentes do Java, surgiram coleções imutáveis, por exemplo, criadas via List.of(), Set.of(), Map.of(). Em versões antigas do Java (antes da 12) e em algumas implementações de terceiros, a serialização dessas coleções pode não funcionar corretamente: após a desserialização, a coleção se torna mutável comum ou até ocorre um erro.
Exemplo:
List<String> list = List.of("a", "b", "c");
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("list.ser"))) {
oos.writeObject(list);
}
Em JVMs antigas, na desserialização ocorria um erro ou a coleção deixava de ser imutável.
Como evitar
- Verifique a documentação da versão do Java que você utiliza.
- Teste a serialização e desserialização dessas coleções.
- Se precisar manter a imutabilidade, após a desserialização, envolva a coleção com Collections.unmodifiableList(list).
5. Serialização de campos transient e static
O que acontece com esses campos:
- transient — campos marcados com essa palavra-chave não são serializados. Após a desserialização, terão o valor padrão (por exemplo, null ou 0).
- static — campos de classe (e não de objeto) nunca são serializados.
Exemplo:
class Book implements Serializable {
String title;
transient String cache; // não é serializado!
static String publisher = "Default"; // também não é serializado!
}
Por que isso é importante
Se você armazena alguns valores computados ou cache dentro do objeto, marque-os como transient — isso economiza espaço e acelera a serialização.
Atenção: Após a desserialização, os campos transient precisam ser recalculados ou inicializados novamente.
6. Serialização de coleções grandes: desempenho e tamanho do arquivo
Problemas:
- Coleções grandes (por exemplo, um milhão de objetos) podem levar a arquivos enormes, tempos longos de gravação e leitura e, às vezes, até falta de memória (OutOfMemoryError).
- Ao serializar um grafo de objetos (por exemplo, coleções complexas inter-relacionadas), o tamanho do arquivo pode crescer inesperadamente.
Como evitar
- Serialize a coleção em partes: por exemplo, grave os objetos um a um ou em pequenos lotes.
- Use processamento em streaming: em vez de serializar a coleção inteira de uma vez, serialize os elementos conforme a necessidade.
- Compacte os arquivos: use GZIPOutputStream para reduzir o tamanho do arquivo.
Exemplo de serialização em streaming:
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("books.ser"))) {
for (Book book : bigList) {
oos.writeObject(book);
}
}
Atenção: Nessa abordagem, a desserialização requer saber quantos objetos foram gravados (ou usar um “marcador de fim” especial).
GO TO FULL VERSION