InvalidOperationException: This JsonSerializerOptions instance is read-only or has already been used in serialization or deserialization is thrown when code changes a JsonSerializerOptions object after that object was passed to JsonSerializer. Finish configuring the options (converters, naming policy and the rest) before the first Serialize or Deserialize call that uses them.

In the reproduction below, a JsonStringEnumConverter was added to an options object that had already serialized one record. Building a second options object with the converter added before its first use made the same serialization work.

The error

{"Name":"David Chen","Status":0}
Unhandled exception. System.InvalidOperationException: This JsonSerializerOptions instance is read-only or has already been used in serialization or deserialization.
   at System.Text.Json.ThrowHelper.ThrowInvalidOperationException_SerializerOptionsReadOnly(JsonSerializerContext context)
   at System.Text.Json.JsonSerializerOptions.VerifyMutable()
   at System.Text.Json.Serialization.ConfigurationList`1.Add(TItem item)
   at Program.<Main>$(String[] args) in C:\...\ro\Program.cs:line 15

The first line is the successful serialization with the untouched options; the exception comes from the next statement. Note what is missing: no path, no line number and no JSON position, because nothing was being read or written when it was thrown. The last stack frame, Program.cs:line 15, is the line to look at.

Why it happens

The code that failed was this, with nothing between the two parts but an if block that did not run:

var options = new JsonSerializerOptions();
Console.WriteLine(JsonSerializer.Serialize(new Patient("David Chen", Status.Active), options));
...
options.Converters.Add(new JsonStringEnumConverter());

The stack trace shows the path: ConfigurationList`1.Add (the Converters list) called JsonSerializerOptions.VerifyMutable(), which threw. The first Serialize call had locked the options, and from then on adding a converter is refused. The message names both ways an options object gets into this state: it is read-only, or it has already been used.

The fix

var fixedOptions = new JsonSerializerOptions();
fixedOptions.Converters.Add(new JsonStringEnumConverter());
Console.WriteLine(JsonSerializer.Serialize(new Patient("David Chen", Status.Active), fixedOptions));

Running dotnet run -- fix printed the old options' result and then the new one:

{"Name":"David Chen","Status":0}
{"Name":"David Chen","Status":"Active"}

The converter is added on the line after new JsonSerializerOptions(), before the options reach the serializer, and the enum now comes out as "Active" instead of 0. If two parts of a program need different settings, give each its own options object, configured completely before its first use, and keep reusing that instance afterwards.

How it was reproduced

A console project from dotnet new console on .NET SDK 10.0.403 (net10.0, no extra packages; System.Text.Json is part of the framework), a Patient record with a Status enum property, one JsonSerializer.Serialize call with a fresh options object and then options.Converters.Add(new JsonStringEnumConverter()) on the same object. Run with dotnet run for the error and dotnet run -- fix for the corrected order.

Frequently asked

Why can I not add a converter to JsonSerializerOptions?
The options object was already passed to JsonSerializer.Serialize or Deserialize. After that first use, adding to Converters calls VerifyMutable, which throws InvalidOperationException. Add converters before the first call.
How do I fix JsonSerializerOptions instance is read-only?
Configure the options completely right after creating them, then use them. If you need different settings later, create a new JsonSerializerOptions object with those settings instead of changing the one already in use.
Does the exception mean the first serialization failed?
No. In the reproduction the first Serialize call printed its JSON normally. The exception came from the next statement, the Converters.Add call on the options that call had used.

More decoded errors in the Fixes category.