NotSerializableException总在反序列化时才报,因为Java序列化仅在ObjectOutputStream.writeObject()或ObjectInputStream.readObject()执行时校验字段是否可序列化,而非编译期;若某字段(如Logger、Thread)未实现Serializable,此时立即抛出异常。

为什么NotSerializableException总在反序列化时才报?
因为 Java 序列化机制只在真正写入/读取字节流时才校验可序列化性——声明 implements Serializable 不等于对象就“安全”,它只检查类本身,不递归检查所有字段类型。一旦某个非 Serializable 的字段(比如 Thread、Socket、自定义未标记接口的类)被序列化器碰到,就会在 ObjectOutputStream.writeObject() 或 ObjectInputStream.readObject() 那一刻抛出 NotSerializableException。
常见错误现象:java.io.NotSerializableException: com.example.MyService —— 说明 MyService 类没实现 Serializable,但它却被当成了某个可序列化对象的字段。
- 检查堆栈里最顶层的异常消息,它指出的是“第一个失败的字段类型”,不是你正在序列化的主对象
- 如果主对象是
ArrayList<MyEntity>,而MyEntity里有个Logger logger = LoggerFactory.getLogger(...)字段,那真正爆掉的是Logger类(SLF4J 的Logger通常不可序列化) - IDE 的“Find Usages”未必能帮你定位到隐式引用,得靠
serialVersionUID变更后重跑序列化测试来暴露深层依赖
transient 不是万能解药,但它是最快止血方式
给不可序列化的字段加 transient 修饰符,能跳过该字段的序列化流程,避免异常。但它不解决逻辑问题:字段值会丢失,反序列化后为 null 或默认值,可能引发 NullPointerException 或业务异常。
使用场景:缓存、连接、回调、日志器、线程局部变量等运行期动态生成的内容。
立即学习“Java免费学习笔记(深入)”;
- 必须确保字段在反序列化后能被安全重建(例如在
readObject()方法里重新初始化) - 不要对
final字段加transient后又指望它有值——final字段反序列化时不走构造器,也不会被赋值,结果一定是默认值 - 如果字段是第三方库类型且不可改源码,优先选
transient+ 自定义readObject(),而不是强行让那个类实现Serializable
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
this.logger = LoggerFactory.getLogger(getClass()); // 重建 transient 字段
}
哪些字段类型天然不支持序列化?
Java 标准库中明确未实现 Serializable 的类型包括:Thread、ClassLoader、FileInputStream、Socket、Connection、Logger(SLF4J/JUL)、DateFormat(非 SimpleDateFormat 子类)、以及几乎所有带 native 资源或 OS 句柄的类。
性能与兼容性影响:强行包装这些类型(比如用 Serializable 接口继承)会导致序列化数据包含无效句柄,反序列化时直接失败,或在不同 JVM 版本间行为不一致。
- 别试图用
writeObject()手动序列化Socket——它底层是 OS 文件描述符,跨进程不存在 -
java.time.*类型(如LocalDateTime)是可序列化的,但老项目若用了org.joda.time.DateTime,得确认其版本是否支持(2.0+ 才实现Serializable) - Spring 的
ApplicationContext、BeanFactory等核心容器对象一律不可序列化,切勿放入 DTO 或 session 属性
怎么提前发现潜在的 NotSerializableException?
靠上线后报错太晚。应该在单元测试里模拟序列化过程,强制触发校验。
- 用
ObjectOutputStream写入ByteArrayOutputStream,再用ObjectInputStream读回,是最小闭环验证 - 如果对象含静态字段,注意它们不会被序列化,也不参与校验——但若静态字段是泛型工具类(如
JsonMapper),被误当成实例字段引用,也会间接导致失败 - 使用
serialver工具检查类的serialVersionUID是否显式声明;未声明时,编译器按类结构自动生成,字段增删会悄悄改变 UID,导致反序列化时抛InvalidClassException,这和NotSerializableException容易混淆
复杂点在于:有些框架(如 RedisTemplate、Kryo)默认不走 Java 原生序列化,它们有自己的规则,但一旦切换回 JdkSerializationRedisSerializer 或显式调用 writeObject,那些被掩盖的字段问题就全浮出来了。








