为何Gson.toJson序列化Exception会抛出StackOverflowError?
为什么Gson序列化异常时抛出StackOverflowError?
这个问题我之前排查过类似的场景,核心原因是Gson默认没有循环引用检测机制,而Java的Throwable类本身包含cause、suppressed这类会触发递归序列化的字段,如果你的应用中的异常对象存在循环引用,就会导致Gson无限递归遍历,最终耗尽栈空间抛出StackOverflowError。
具体原因分析
你单独测试NullPointerException没问题,是因为默认的NPE的cause为null,递归深度有限。但在你的应用中,异常对象大概率因为业务逻辑出现了循环引用:
- 比如自定义异常被错误地将
cause设置为自身(ex.initCause(ex)) - 或者异常的
suppressed列表中包含了当前异常 - 甚至嵌套异常链形成闭环(A的cause是B,B的cause是A)
Gson序列化时会递归处理这些关联字段,没有内置的循环拦截逻辑,所以会一直递归直到栈溢出。
解决方案
方案1:修复异常对象的循环引用问题
先排查应用中异常的构造和抛出逻辑,看看是不是哪里错误地让异常形成了循环引用。比如:
// 错误示例:导致循环引用的代码 CustomBizException ex = new CustomBizException("业务异常"); ex.initCause(ex); // 这里把异常自身设为cause,序列化时会无限递归
如果能找到这类逻辑,直接修复是最根本的解决办法。
方案2:自定义Gson TypeAdapter处理Throwable
如果无法避免异常的循环引用(或者需要兼容这类场景),可以自定义TypeAdapter手动控制Throwable的序列化过程,用集合跟踪已处理的异常,避免循环递归。
示例代码如下:
import com.google.gson.TypeAdapter; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; import java.io.IOException; import java.util.HashSet; import java.util.Set; public class SafeThrowableTypeAdapter extends TypeAdapter<Throwable> { @Override public void write(JsonWriter out, Throwable value) throws IOException { if (value == null) { out.nullValue(); return; } // 用Set记录已序列化的异常,避免循环引用 Set<Throwable> serializedExceptions = new HashSet<>(); serializeThrowable(out, value, serializedExceptions); } private void serializeThrowable(JsonWriter out, Throwable value, Set<Throwable> serialized) throws IOException { // 已序列化过的异常直接输出null,终止递归 if (value == null || serialized.contains(value)) { out.nullValue(); return; } serialized.add(value); out.beginObject(); // 序列化核心信息 out.name("message").value(value.getMessage()); out.name("exceptionClass").value(value.getClass().getName()); // 序列化堆栈跟踪 out.name("stackTrace"); out.beginArray(); for (StackTraceElement element : value.getStackTrace()) { out.beginObject(); out.name("className").value(element.getClassName()); out.name("methodName").value(element.getMethodName()); out.name("fileName").value(element.getFileName()); out.name("lineNumber").value(element.getLineNumber()); out.endObject(); } out.endArray(); // 递归序列化cause out.name("cause"); serializeThrowable(out, value.getCause(), serialized); // 递归序列化suppressed异常 out.name("suppressed"); out.beginArray(); for (Throwable suppressed : value.getSuppressed()) { serializeThrowable(out, suppressed, serialized); } out.endArray(); out.endObject(); serialized.remove(value); } @Override public Throwable read(JsonReader in) throws IOException { // 不需要反序列化异常的话直接返回null,需要的话可自行实现逻辑 return null; } }
然后在创建Gson实例时注册这个适配器:
import com.google.gson.Gson; import com.google.gson.GsonBuilder; public class GsonFactory { public static Gson createSafeGson() { return new GsonBuilder() .registerTypeAdapter(Throwable.class, new SafeThrowableTypeAdapter()) .create(); } }
这样无论异常是否存在循环引用,都能安全完成序列化,不会触发栈溢出。
总结
你的问题本质是Gson默认行为不处理循环引用,而应用中的异常恰好形成了递归闭环。要么修复异常的构造逻辑,要么通过自定义TypeAdapter规避循环递归问题。
内容的提问来源于stack exchange,提问作者Bikas Katwal
相关产品推荐
相关产品推荐

