Skip to main content

Non-Tree Models and Recursion Detection

The Juneau Serializer API is designed to be used against POJO tree structures. It expects that there not be loops in the POJO model (e.g. children with references to parents, etc...).

If you serialize a model that contains a loop, the default configuration does not throw an exception. Instead, the traversal is silently truncated once MarshallingTraverseContext.Builder.maxDepth(int) (default 100) is reached, producing finite but semantically-incomplete output. maxDepth is a size guard, not a cycle detector. (Only if the stack is exhausted before maxDepth is reached will a StackOverflowError occur; it is converted to a serialize exception advising you to enable recursion detection.)

If you want to handle loops explicitly instead of relying on maxDepth truncation, Juneau provides two opt-in settings.

For the examples below, let's make a POJO model out of the following classes:

public class A {
public B b;
}

public class B {
public C c;
}

public class C {
public A a;
}

Now we create a model with a loop:

// Create a recursive loop.
A a = new A();
a.b = new B();
a.b.c = new C();
a.b.c.a = a;

Fail-fast with detectRecursions()​

The MarshallingTraverseContext.Builder.detectRecursions() setting tells the serializer to look for instances of an object already in the current branch of the tree. When a duplicate is encountered, serialization fails fast with a SerializeException (wrapping a MarshallingRecursionException whose message is "Recursion occurred, stack=...").

// Clone an existing serializer and enable recursion detection.
WriterSerializer serializer = Json5Serializer
.DEFAULT_READABLE
.copy()
.detectRecursions()
.build();

// Throws a SerializeException ("Recursion occurred, stack=...") instead of silently truncating.
String json = serializer.write(a);

Skip the loop with ignoreRecursions()​

The MarshallingTraverseContext.Builder.ignoreRecursions() setting tells the serializer to omit an object when it is already in the current branch of the tree, allowing serialization to complete.

// Clone an existing serializer and enable ignoring of recursions.
WriterSerializer serializer = Json5Serializer
.DEFAULT_READABLE
.copy()
.ignoreRecursions()
.build();

// Serialize to JSON.
String json = serializer.write(a);

What we end up with is the following, which does not serialize the contents of the recursive a reference:

{
b: {
c: {
}
}
}
note

Recursion detection introduces a performance penalty of around 20%. For this reason both settings are disabled by default.