A possible object cycle was detected. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 64.
from an API that returns EF Core entities means the JSON serializer went round a navigation loop:
order, customer, the customer's orders, the same order again. Return a DTO shaped for the response
instead of the entity, and the loop disappears.
A Select into a small record fixes it and also stops the API leaking every column of
every related table. ReferenceHandler.IgnoreCycles silences the error in one line,
at a cost described below.
The error
System.Text.Json.JsonException: A possible object cycle was detected. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 64. Consider using ReferenceHandler.Preserve on JsonSerializerOptions to support cycles. Path: $.Customer.Orders.Customer.Orders.Customer.Orders.Customer.Orders.Customer.Orders.Customer.Orders.Customer.Orders.Customer.Orders.Customer.Orders.Customer.Orders.Customer.Orders.Customer.Orders.Customer.Orders.Customer.Orders.Customer.Orders.Customer.Orders.Customer.Orders.Customer.Orders.Customer.Orders.Customer.Orders.Customer.Orders.
at System.Text.Json.ThrowHelper.ThrowJsonException_SerializerCycleDetected(Int32 maxDepth)
The path is not cut off here; the serializer really wrote
.Customer.Orders until it reached depth 64. The client received a 500.
Why it happens
The endpoint returned entities straight from the query:
app.MapGet("/orders-with-customers", async (OrdersDb db) =>
await db.Orders.Include(o => o.Customer).ToListAsync());
Include fills each order's Customer. Because this is a tracking query,
EF Core also runs relationship fix-up: every loaded order is added to its customer's
Orders list. Maria Garcia's two orders now point at her, and she points back at
them. System.Text.Json, by default, does not remember which objects it has already written; it
follows Customer, then Orders, then Customer again, and
stops with this exception when the nesting passes 64 levels.
The fix
public record OrderDto(int Id, DateTime PlacedAt, decimal Total, string CustomerName);
app.MapGet("/orders", async (OrdersDb db) =>
await db.Orders
.Select(o => new OrderDto(o.Id, o.PlacedAt, o.Total, o.Customer.Name))
.ToListAsync());
No Include is needed: EF Core joins the customers table itself and selects only the
four columns the record needs. Each order now serializes as flat JSON, for example
{"id":1,"placedAt":"2026-09-20T10:15:00Z","total":42.50,"customerName":"Maria Garcia"}.
The quick alternative is a serializer setting:
builder.Services.ConfigureHttpJsonOptions(o =>
o.SerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles);
The same entity endpoint then returned 200, but look at what it wrote for the first order:
{"id":1,"placedAt":"2026-09-20T10:15:00Z","total":42.50,"customerId":1,"customer":{"id":1,"name":"Maria Garcia","orders":[null,{"id":2,"placedAt":"2026-09-22T16:40:00Z","total":18.00,"customerId":1,"customer":null}]}}
Where the cycle would repeat, the serializer writes null, including a
null inside the orders array that a client will have to skip. The
payload still carries every entity column, and its shape depends on what the context happened to
load. The message's own suggestion, ReferenceHandler.Preserve, also returned 200,
but it wrapped the list in an object with $id and $values and replaced
repeated orders with $ref entries, which a client has to resolve itself before it
can use the data.
How it was reproduced
A minimal API from dotnet new web with Microsoft.EntityFrameworkCore 10.0.12 and
Npgsql.EntityFrameworkCore.PostgreSQL 10.0.3, a Customer with a list of
Order and an Order with its Customer, seeded with two
fictional customers and three orders. The GET that returned the included entities answered 500
with the exception above in the server log; the DTO endpoint answered 200. .NET SDK 10.0.401
and ASP.NET Core 10.0.12 on Windows, PostgreSQL 18.4 in the postgres:18 Docker
image.
Frequently asked
- How do I fix a possible object cycle was detected in ASP.NET Core?
- Stop returning EF Core entities with navigation properties in both directions. Project the query into a DTO or record that holds only the fields the client needs, for example with Select. The serializer then has no loop to follow.
- Is ReferenceHandler.IgnoreCycles a good fix for the object cycle error?
- It removes the exception but writes null wherever the cycle would repeat, including null items inside collections, and it still exposes every entity column. It is acceptable for a quick internal tool; a DTO is the better fix for an API.
- Why does Include cause a JSON object cycle in EF Core?
- In a tracking query EF Core fixes up relationships in both directions, so an order's customer also gets that order in its Orders collection. Serializing the entity then walks from order to customer to orders and back again until the depth limit of 64.
More decoded errors in the Fixes category. When to reach for
Include and when for a projection is one of the questions in
the SQL and EF Core interview questions.