103

I would like to generate a JSON String from my object:

Gson gson = new Gson();
String json = gson.toJson(item);

Everytime I try to do this, I get this error:

14:46:40,236 ERROR [[BomItemToJSON]] Servlet.service() for servlet BomItemToJSON threw exception
java.lang.StackOverflowError
    at com.google.gson.stream.JsonWriter.string(JsonWriter.java:473)
    at com.google.gson.stream.JsonWriter.writeDeferredName(JsonWriter.java:347)
    at com.google.gson.stream.JsonWriter.value(JsonWriter.java:440)
    at com.google.gson.internal.bind.TypeAdapters$7.write(TypeAdapters.java:235)
    at com.google.gson.internal.bind.TypeAdapters$7.write(TypeAdapters.java:220)
    at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:89)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:200)
    at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68)
    at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.write(CollectionTypeAdapterFactory.java:96)
    at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.write(CollectionTypeAdapterFactory.java:60)
    at com.google.gson.Gson$FutureTypeAdapter.write(Gson.java:843)

These are the attributes of my BomItem class:

private int itemId;
private Collection<BomModule> modules;
private boolean deprecated;
private String partNumber;
private String description; //LOB
private int quantity;
private String unitPriceDollar;
private String unitPriceEuro;
private String discount; 
private String totalDollar;
private String totalEuro;
private String itemClass;
private String itemType;
private String vendor;
private Calendar listPriceDate;
private String unitWeight;
private String unitAveragePower;
private String unitMaxHeatDissipation;
private String unitRackSpace;

Attributes of my referenced BomModule class:

private int moduleId;
private String moduleName;
private boolean isRootModule;
private Collection<BomModule> parentModules;
private Collection<BomModule> subModules;
private Collection<BomItem> items;
private int quantity;

Any idea what causes this error? How can I fix it?

nimrod
  • 5,595
  • 29
  • 85
  • 149

15 Answers15

97

That problem is that you have a circular reference.

In the BomModule class you are referencing to:

private Collection<BomModule> parentModules;
private Collection<BomModule> subModules;

That self reference to BomModule, obviously, not liked by GSON at all.

A workaround is just set the modules to null to avoid the recursive looping. This way I can avoid the StackOverFlow-Exception.

item.setModules(null);

Or mark the fields you don't want to show up in the serialized json by using the transient keyword, eg:

private transient Collection<BomModule> parentModules;
private transient Collection<BomModule> subModules;
Ren
  • 3,395
  • 2
  • 27
  • 46
SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
  • Yes a BomModule object can be part of another BomModule object. – nimrod Apr 18 '12 at 13:06
  • But is that a problem? 'Collection modules' is only a collection, and I think gson should be able to make a simple array out of it? – nimrod Apr 18 '12 at 13:11
  • @dooonot: Do any of the objects in the collection reference their parent object? – SLaks Apr 18 '12 at 13:15
  • I am not sure if I got you right, but yes. Please see the updated question above. – nimrod Apr 18 '12 at 13:17
  • @dooonot: As I suspected, it goes into an infinite loop when serializing the parent and child collections. What kind JSON do you expect it to write? – SLaks Apr 18 '12 at 13:19
  • I just want to have the moduleIds in the array. Something like this: Json: {"itemId":63788,"modules":[60000, 60001, 60002, 60003],"deprecated":false,"partNumber":"xx-xx-xxxx","description":bla bla bla","quantity":0,"unitPriceDollar":"$350.00","discount":"10%","totalDollar":"$0.00","itemClass":"Server","itemType":"HW","vendor":"Sun"} - any way how to approach this? – nimrod Apr 18 '12 at 13:23
  • Ass a workaround I just set the modules to null to avoid the recursive looping. (item.setModules(null)). This way I can avoid the StackOverFlow-Exception. Thanks for the hint dude! – nimrod Apr 18 '12 at 13:46
  • item.setModules(null) made my day :) Thanks. – zytham Apr 04 '13 at 10:27
32

I had this problem when I had a Log4J logger as a class property, such as:

private Logger logger = Logger.getLogger(Foo.class);

This can be solved by either making the logger static or simply by moving it into the actual function(s).

BenMorel
  • 34,448
  • 50
  • 182
  • 322
Zar
  • 6,786
  • 8
  • 54
  • 76
  • 4
    Absolutely great catch. That self reference to the class obviously not liked by GSON at all. Saved me a lot of headaches! +1 – christopher Nov 19 '14 at 20:54
  • 1
    another way to solve it, is by adding transient modifier to the field – gawi Nov 17 '16 at 16:03
  • 1
    logger should mostly be static. Otherwise you will incur the cost of getting that Logger instance for each object creation, which is probably not what you want. (The cost isn't trivial) – stolsvik Apr 19 '18 at 09:42
28

If you're using Realm and you get this error, and the object giving the trouble extends RealmObject, don't forget to do realm.copyFromRealm(myObject) to create a copy without all the Realm bindings before passing through to GSON for serialization.

I'd missed doing this for just one amongst a bunch of objects being copied... took me ages to realise as the stack trace doesn't name the object class/type. Thing is, the issue is caused by a circular reference, but it's a circular reference somewhere in the RealmObject base class, not your own subclass, which makes it harder to spot!

Breeno
  • 3,007
  • 2
  • 31
  • 30
  • 1
    That's correct! In my case change my object list queried directly from realm to ArrayList copyList = new ArrayList<>(); for(Image image : images){ copyList.add(realm.copyFromRealm(image)); } – Ricardo Mutti May 25 '17 at 20:11
  • Using realm, that was exactly the solution that solves the problem, thanks – Jude Fernandes Oct 20 '18 at 08:18
13

As SLaks said StackOverflowError happen if you have circular reference in your object.

To fix it you could use TypeAdapter for your object.

For example, if you need only generate String from your object you could use adapter like this:

class MyTypeAdapter<T> extends TypeAdapter<T> {
    public T read(JsonReader reader) throws IOException {
        return null;
    }

    public void write(JsonWriter writer, T obj) throws IOException {
        if (obj == null) {
            writer.nullValue();
            return;
        }
        writer.value(obj.toString());
    }
}

and register it like this:

Gson gson = new GsonBuilder()
               .registerTypeAdapter(BomItem.class, new MyTypeAdapter<BomItem>())
               .create();

or like this, if you have interface and want to use adapter for all its subclasses:

Gson gson = new GsonBuilder()
               .registerTypeHierarchyAdapter(BomItemInterface.class, new MyTypeAdapter<BomItemInterface>())
               .create();
borislubimov
  • 131
  • 1
  • 3
12

My answer is a little bit late, but I think this question doesn't have a good solution yet. I found it originally here.

With Gson you can mark the fields you do want to be included in json with @Expose like this:

@Expose
String myString;  // will be serialized as myString

and create the gson object with:

Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();

Circular references you just do not expose. That did the trick for me!

ffonz
  • 1,304
  • 1
  • 11
  • 29
  • Do you know if there is an annotation that does the opposite of this? There are like 4 fields I need to ignore and over 30 I need to include. – wheeleruniverse Aug 06 '18 at 11:49
  • @jDub9 Sorry for my late answer, but I have been on holiday. Have a look at [this](https://stackoverflow.com/a/5889590/1770778) answer. Hope it solves your problem – ffonz Aug 21 '18 at 13:15
3

This error is common when you have a logger in your super class. As @Zar suggested before, you can use static for your logger field, but this also works:

protected final transient Logger logger = Logger.getLogger(this.getClass());

P.S. probably it will work and with @Expose annotation check more about this here: https://stackoverflow.com/a/7811253/1766166

Community
  • 1
  • 1
zygimantus
  • 3,649
  • 4
  • 39
  • 54
1

I have the same problem. In my case the reason was that constructor of my serialized class take context variable, like this:

public MetaInfo(Context context)

When I delete this argument, error has gone.

public MetaInfo()
Denis
  • 519
  • 2
  • 5
  • 12
1

Edit: Sorry for my bad, this is my first answer. Thanks for your advises.

I create my own Json Converter

The main solution I used is to create a parents object set for each object reference. If a sub-reference points to existed parent object, it will discard. Then I combine with an extra solution, limiting the reference time to avoid infinitive loop in bi-directional relationship between entities.

My description is not too good, hope it helps you guys.

This is my first contribution to Java community (solution to your problem). You can check it out ;) There is a README.md file https://github.com/trannamtrung1st/TSON

  • 2
    A link to a solution is welcome, but please ensure your answer is useful without it: [add context around the link](//meta.stackexchange.com/a/8259) so your fellow users will have some idea what it is and why it’s there, then quote the most relevant part of the page you're linking to in case the target page is unavailable. [Answers that are little more than a link may be deleted.](//stackoverflow.com/help/deleted-answers) – Paul Roub Jul 23 '18 at 04:13
  • 2
    Self Promotion Just linking to your own library or tutorial is not a good answer. Linking to it, explaining why it solves the problem, providing code on how to do so and disclaiming that you wrote it makes for a better answer. See: [What signifies “Good” self promotion?](https://meta.stackexchange.com/q/182212) – 4b0 Jul 23 '18 at 04:14
  • Thanks so much. I had edit my answer. Hope it would be fine :D – Trần Nam Trung Jul 23 '18 at 04:26
  • Similar to what the other commenters said, it is preferred that you show the most important parts of your code in your post. Also, you don't need to apologize for mistakes in your answer. – 0xCursor Jul 23 '18 at 04:42
1

For Android users, you cannot serialize a Bundle due to a self-reference to Bundle causing a StackOverflowError.

To serialize a bundle, register a BundleTypeAdapterFactory.

Cord Rehn
  • 1,119
  • 14
  • 22
0

In Android, gson stack overflow turned out to be the declaration of a Handler. Moved it to a class that isn't being deserialized.

Based on Zar's recommendation, I made the the handler static when this happened in another section of code. Making the handler static worked as well.

Dan
  • 485
  • 6
  • 9
0

BomItem refers to BOMModule (Collection<BomModule> modules), and BOMModule refers to BOMItem (Collection<BomItem> items). Gson library doesn't like circular references. Remove this circular dependency from your class. I too had faced same issue in the past with gson lib.

Binita Bharati
  • 5,239
  • 1
  • 43
  • 24
0

I had this problem occur for me when I put:

Logger logger = Logger.getLogger( this.getClass().getName() );

in my object...which made perfect sense after an hour or so of debugging!

keesp
  • 301
  • 2
  • 13
0

Avoid unnecessary workarounds, like setting values to null or making fields transient. The right way to do this, is to annotate one of the fields with @Expose and then tell Gson to serialize only the fields with the annotation:

private Collection<BomModule> parentModules;
@Expose
private Collection<BomModule> subModules;

...
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
Ismael Sarmento
  • 844
  • 1
  • 11
  • 22
0

I had a similar issue where the class had an InputStream variable which I didn't really have to persist. Hence changing it to Transient solved the issue.

0

After some time fighting with this issue, I believe i have a solution. Problem is in unresolved bidirectional connections, and how to represent connections when they are being serialized. The way to fix that behavior is to "tell" gson how to serialize objects. For that purpose we use Adapters.

By using Adapters we can tell gson how to serialize every property from your Entity class as well as which properties to serialize.

Let Foo and Bar be two entities where Foo has OneToMany relation to Bar and Bar has ManyToOne relation to Foo. We define Bar adapter so when gson serializes Bar, by defining how to serialize Foo from perspective of Bar cyclic referencing will not be possible.

public class BarAdapter implements JsonSerializer<Bar> {
    @Override
    public JsonElement serialize(Bar bar, Type typeOfSrc, JsonSerializationContext context) {
        JsonObject jsonObject = new JsonObject();
        jsonObject.addProperty("id", bar.getId());
        jsonObject.addProperty("name", bar.getName());
        jsonObject.addProperty("foo_id", bar.getFoo().getId());
        return jsonObject;
    }
}

Here foo_id is used to represent Foo entity which would be serialized and which would cause our cyclic referencing problem. Now when we use adapter Foo will not be serialized again from Bar only its id will be taken and put in JSON. Now we have Bar adapter and we can use it to serialize Foo. Here is idea:

public String getSomething() {
    //getRelevantFoos() is some method that fetches foos from database, and puts them in list
    List<Foo> fooList = getRelevantFoos();

    GsonBuilder gsonBuilder = new GsonBuilder();
    gsonBuilder.registerTypeAdapter(Bar.class, new BarAdapter());
    Gson gson = gsonBuilder.create();

    String jsonResponse = gson.toJson(fooList);
    return jsonResponse;
}

One more thing to clarify, foo_id is not mandatory and it can be skipped. Purpose of adapter in this example is to serialize Bar and by putting foo_id we showed that Bar can trigger ManyToOne without causing Foo to trigger OneToMany again...

Answer is based on personal experience, therefore feel free to comment, to prove me wrong, to fix mistakes, or to expand answer. Anyhow I hope someone will find this answer useful.

quepasa
  • 9
  • 3
  • Defining adapter for handle serialization proecss itself is another way of handling the cyclic dependency. You have option for it though there are other annotations which can prevent happening this instead of writing the adapters. – Sariq Shaikh Mar 08 '20 at 21:41