You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 06:42:44