This is an automated email from the ASF dual-hosted git repository.
jamesbognar pushed a commit to branch docs
in repository https://gitbox.apache.org/repos/asf/juneau.git
The following commit(s) were added to refs/heads/docs by this push:
new 9485b086ca Document maxDepth truncation + @ParentProperty cyclic-graph
serialization contract (detectRecursions/ignoreRecursions).
9485b086ca is described below
commit 9485b086cae205aaeeefd1ddfcc441e6999b374b
Author: James Bognar <[email protected]>
AuthorDate: Fri Jul 17 10:38:44 2026 -0400
Document maxDepth truncation + @ParentProperty cyclic-graph serialization
contract (detectRecursions/ignoreRecursions).
Co-authored-by: Cursor <[email protected]>
---
pages/topics/03.03.08.ParentPropertyAnnotation.md | 10 +++++
pages/topics/03.17.Recursion.md | 55 ++++++++++++++++-------
2 files changed, 49 insertions(+), 16 deletions(-)
diff --git a/pages/topics/03.03.08.ParentPropertyAnnotation.md
b/pages/topics/03.03.08.ParentPropertyAnnotation.md
index 44a1d24d99..92ffb3a61b 100644
--- a/pages/topics/03.03.08.ParentPropertyAnnotation.md
+++ b/pages/topics/03.03.08.ParentPropertyAnnotation.md
@@ -64,3 +64,13 @@ public class Person {
- The parser automatically calls the setter or sets the field with a reference
to the parent object
- This allows child objects to navigate back to their parent if needed
- Useful for bidirectional relationships where child objects need access to
their parent context
+
+## Cyclic Graphs and Serialization
+
+When a `@ParentProperty` back-reference is also a normally-visible bean
property (e.g. a `public` field or getter, as in the field example above), it
forms a parent/child **cycle**. Unlike Jackson's `@JsonBackReference`, Juneau
does **not** auto-omit the `@ParentProperty` member on the write (serialize)
side — the annotation is a parse-time convenience only. This is intentional.
+
+The serialization behavior of such a cyclic graph therefore depends on the
serializer's recursion settings (see [Non-Tree Models and Recursion
Detection](/docs/topics/Recursion)):
+
+- **Default configuration** (`detectRecursions=false`,
`ignoreRecursions=false`): the traversal is silently truncated at <a
href="/site/apidocs/org/apache/juneau/marshall/MarshallingTraverseContext.Builder.html#maxDepth(int)"
target="_blank">maxDepth</a> (default `100`), producing finite but
semantically-incomplete output. No exception is thrown — `maxDepth` is a size
guard, not a cycle detector.
+- <a
href="/site/apidocs/org/apache/juneau/marshall/MarshallingTraverseContext.Builder.html#detectRecursions()"
target="_blank">detectRecursions()</a>: fail-fast on the cycle with a
`SerializeException` ("Recursion occurred, stack=...").
+- <a
href="/site/apidocs/org/apache/juneau/marshall/MarshallingTraverseContext.Builder.html#ignoreRecursions()"
target="_blank">ignoreRecursions()</a>: omit the repeated node so the output
round-trips cleanly — parsing re-injects the parent via `@ParentProperty`.
diff --git a/pages/topics/03.17.Recursion.md b/pages/topics/03.17.Recursion.md
index cec1a25193..aebaa78329 100644
--- a/pages/topics/03.17.Recursion.md
+++ b/pages/topics/03.17.Recursion.md
@@ -6,13 +6,15 @@ slug: Recursion
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 try to serialize models with loops, you will usually cause a
`StackOverflowError` to be thrown (if <a
href="/site/apidocs/org/apache/juneau/marshall/MarshallingTraverseContext.Builder.html#maxDepth(int)"
target="_blank">MarshallingTraverseContext.Builder.maxDepth(int)</a> is not
reached first).
-If you still want to use the Juneau serializers on such models, Juneau
provides the <a
href="/site/apidocs/org/apache/juneau/marshall/MarshallingTraverseContext.Builder.html#detectRecursions()"
target="_blank">MarshallingTraverseContext.Builder.detectRecursions()</a>
setting.
-It tells the serializer to look for instances of an object in the current
branch of the tree and skip serialization when
-a duplicate is encountered.
+If you serialize a model that contains a loop, the **default** configuration
does **not** throw an exception.
+Instead, the traversal is silently truncated once <a
href="/site/apidocs/org/apache/juneau/marshall/MarshallingTraverseContext.Builder.html#maxDepth(int)"
target="_blank">MarshallingTraverseContext.Builder.maxDepth(int)</a> (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.)
-For example, let's make a POJO model out of the following classes:
+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:
```java
public class A {
@@ -28,27 +30,50 @@ public class C {
}
```
-Now we create a model with a loop and serialize the results.
+Now we create a model with a loop:
+
+```java
+// 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 <a
href="/site/apidocs/org/apache/juneau/marshall/MarshallingTraverseContext.Builder.html#detectRecursions()"
target="_blank">MarshallingTraverseContext.Builder.detectRecursions()</a>
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=..."`).
```java
-// Clone an existing serializer and set property for detecting recursions.
+// Clone an existing serializer and enable recursion detection.
WriterSerializer serializer = Json5Serializer
.DEFAULT_READABLE
.copy()
.detectRecursions()
.build();
-// Create a recursive loop.
-A a = new A();
-a.b = new B();
-a.b.c = new C();
-a.b.c.a = a;
+// Throws a SerializeException ("Recursion occurred, stack=...") instead of
silently truncating.
+String json = serializer.serialize(a);
+```
+
+## Skip the loop with ignoreRecursions()
+
+The <a
href="/site/apidocs/org/apache/juneau/marshall/MarshallingTraverseContext.Builder.html#ignoreRecursions()"
target="_blank">MarshallingTraverseContext.Builder.ignoreRecursions()</a>
setting tells the serializer to omit an object when it is already in the
current branch of the tree, allowing serialization to complete.
+
+```java
+// Clone an existing serializer and enable ignoring of recursions.
+WriterSerializer serializer = Json5Serializer
+ .DEFAULT_READABLE
+ .copy()
+ .ignoreRecursions()
+ .build();
// Serialize to JSON.
String json = serializer.serialize(a);
```
-What we end up with is the following, which does not serialize the contents of
the `c` field:
+What we end up with is the following, which does not serialize the contents of
the recursive `a` reference:
```js
{
@@ -59,9 +84,7 @@ What we end up with is the following, which does not
serialize the contents of t
}
```
-Without recursion detection enabled, this would cause a stack-overflow error.
-
:::note
Recursion detection introduces a performance penalty of around 20%.
-For this reason the setting is disabled by default.
+For this reason both settings are disabled by default.
:::
\ No newline at end of file