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

Reply via email to