quarkusio/quarkus · error · RuntimeException
Cyclic dependency when marshaling an object
Error message
Cyclic dependency when marshaling an object
What it means
HalEntityWrapperJsonbSerializer serializes a HAL entity with JSON-B's custom SerializationContext. To detect infinite recursion it calls ProcessingContext.addProcessedObject(entity); if the entity is already being processed (a reference cycle), it throws RuntimeException 'Cyclic dependency when marshaling an object'.
Source
Thrown at extensions/hal/runtime/src/main/java/io/quarkus/hal/HalEntityWrapperJsonbSerializer.java:23
import jakarta.json.bind.serializer.JsonbSerializer;
import jakarta.json.bind.serializer.SerializationContext;
import jakarta.json.stream.JsonGenerator;
import org.eclipse.yasson.internal.ProcessingContext;
import org.eclipse.yasson.internal.model.ClassModel;
import org.eclipse.yasson.internal.model.PropertyModel;
// Using the raw type here as eclipse yasson doesn't like custom serializers for
// generic root types, see https://github.com/eclipse-ee4j/yasson/issues/639
public class HalEntityWrapperJsonbSerializer implements JsonbSerializer<HalEntityWrapper> {
@Override
public void serialize(HalEntityWrapper wrapper, JsonGenerator generator, SerializationContext context) {
ProcessingContext processingContext = (ProcessingContext) context;
Object entity = wrapper.getEntity();
if (!processingContext.addProcessedObject(entity)) {
throw new RuntimeException("Cyclic dependency when marshaling an object");
}
try {
generator.writeStartObject();
ClassModel classModel = processingContext.getMappingContext().getOrCreateClassModel(entity.getClass());
for (PropertyModel property : classModel.getSortedProperties()) {
if (property.isReadable()) {
writeValue(property.getWriteName(), property.getValue(entity), generator, context);
}
}
writeLinks(wrapper.getLinks(), generator, context);
generator.writeEnd();
} finally {
processingContext.removeProcessedObject(entity);
}
}View on GitHub (pinned to e1c734241f)
Solutions
- Annotate the back-referencing field with @JsonbTransient to break the cycle
- Restructure DTOs so the serialized view has no cycles
- Use @JsonbNillable/filtered views or manual mapping to plain DTOs
- Serialize only one side of the relationship
Example fix
// before
class Node {
public Node parent;
public List<Node> children;
}
// after
class Node {
@JsonbTransient
public Node parent;
public List<Node> children;
} Defensive patterns
Strategy: validation
Validate before calling
Set<Object> seen = new IdentityHashMap<>();
Deque<Object> stack = new ArrayDeque<>(List.of(root));
while (!stack.isEmpty()) {
Object o = stack.pop();
if (!seen.add(o)) throw new IllegalStateException("Cycle detected at " + o.getClass());
for (Field f : o.getClass().getDeclaredFields()) {
if (!f.getType().isPrimitive() && !f.getType().getName().startsWith("java.")) {
f.setAccessible(true);
Object v = f.get(o);
if (v != null) stack.push(v);
}
}
} Type guard
boolean isAcyclic(Object root, Set<Object> seen) {
if (root == null || !seen.add(root)) return root != null ? false : true;
for (Field f : root.getClass().getDeclaredFields()) {
try {
if (!f.getType().isPrimitive() && !f.getType().getName().startsWith("java.")) {
f.setAccessible(true);
Object v = f.get(root);
if (v != null && !isAcyclic(v, seen)) return false;
}
} catch (IllegalAccessException ignored) { }
}
return true;
} Try / catch
try {
jsonb.toJson(halWrapper);
} catch (RuntimeException e) {
if (e.getMessage().contains("Cyclic dependency")) {
throw new IllegalStateException("Entity graph has a cycle; add @JsonbTransient to the back-reference", e);
}
throw e;
} Prevention
- Annotate bidirectional JPA back-references with @JsonbTransient
- Map entities to cycle-free DTOs for REST responses
- Test serialization of the full entity graph in dev
When it happens
Trigger: Serializing an entity graph with JSON-B where an object references itself directly or indirectly (e.g. parent<->child relations both mapped) while wrapped in a HAL entity.
Common situations: Bidirectional JPA relations serialized without @JsonbTransient on the back-reference; self-referencing tree nodes; lazy relations forming a cycle.
Related errors
- Failed to record call to method ${call.method}
- Failed to substitute ${param}
- Cannot serialise field '${i.getName()}' on object '${param}'
- Couldn't load object of type ${i.propertyType.getName()} for
- Cannot instantiate JSON-B component:
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/4524cc338e01cc4e.
Report an issue: GitHub.