Tips
JSON is recommended persistent format to store data in database for greenfield projects. You should also strongly consider setting useProperties to true to restrict key-values to be strings.
Tips
System.Text.Json serialization is built into the Quartz package and is the default; see Serialization (System.Text.Json).
JSON.NET
Quartz.Serialization.Newtonsoft provides JSON serialization support for job stores using Json.NET to handle the actual serialization process.
Installation
You need to add NuGet package reference to your project which uses Quartz.
Install-Package Quartz.Serialization.Newtonsoft
Configuring
Classic property-based configuration
var properties = new NameValueCollection
{
["quartz.jobStore.type"] = "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz",
["quartz.serializer.type"] = "json"
};
ISchedulerFactory schedulerFactory = new StdSchedulerFactory(properties);
Configuring using scheduler builder
ISchedulerFactory schedulerFactory = QuartzSchedulerBuilder.Create()
.Configure(q => q.UsePersistentStore(store =>
{
store.UseGenericDatabase("MyProvider", "my connection string");
// it's generally recommended to stick with
// string property keys and values when serializing
store.Configure(options => options.UseProperties = true);
store.UseNewtonsoftJsonSerializer();
}))
.Build();
UseGenericDatabase is the right method only for a database Quartz has no specific support for; use UseSqlServer, UsePostgres and the rest otherwise. If Quartz ships no description of your ADO.NET driver either, describe it in the same call — see the configuration reference.
Migrating from binary serialization
Quartz 4 no longer ships the BinaryObjectSerializer: the underlying BinaryFormatter has been removed from modern .NET and throws on .NET 9 and later. If you still have binary-serialized data in your database you need to migrate it to JSON.
The recommended path is to perform the migration while you are still on Quartz 3.x, which still includes BinaryObjectSerializer - see the Quartz 3.x version of this page for a ready-made hybrid serializer. Either let the system migrate gradually as it runs, or write a small program that loads and writes back every serialized asset in the database.
If you must read legacy binary data after upgrading to Quartz 4 on .NET 9 or later, you can re-enable BinaryFormatter with Microsoft's unsupported compatibility package. Because the package does not change BinaryFormatter's type identity, only your application project needs it - Quartz itself does not reference it:
<PropertyGroup>
<EnableUnsafeBinaryFormatterSerialization>true</EnableUnsafeBinaryFormatterSerialization>
</PropertyGroup>
<ItemGroup>
<!-- match the package version to your application's target framework, e.g. 9.0.x on net9.0 -->
<PackageReference Include="System.Runtime.Serialization.Formatters" Version="10.0.0" />
</ItemGroup>
The package restores a working - but still unsafe - BinaryFormatter, so read the Microsoft guidance before relying on it and remove it once the migration is complete. The Quartz types keep their [Serializable] / ISerializable support, so the hybrid serializer below can read the old binary payloads and write everything back as JSON.
Example hybrid serializer
using System.Runtime.Serialization.Formatters.Binary;
using Newtonsoft.Json;
using Quartz.Simpl;
using Quartz.Spi;
namespace Quartz;
public sealed class MigratorSerializer : IObjectSerializer
{
// you might need custom configuration, see sections about customizing in documentation
private readonly NewtonsoftJsonObjectSerializer jsonSerializer = new();
public void Initialize() => jsonSerializer.Initialize();
public T DeSerialize<T>(byte[] data) where T : class
{
try
{
// Attempt to deserialize data as JSON
return jsonSerializer.DeSerialize<T>(data)!;
}
catch (JsonReaderException)
{
// The data was not JSON, so fall back to the legacy binary format. This branch needs
// the System.Runtime.Serialization.Formatters compatibility package and
// EnableUnsafeBinaryFormatterSerialization to be set in the application project.
using var stream = new MemoryStream(data);
#pragma warning disable SYSLIB0011
var binaryData = (T) new BinaryFormatter().Deserialize(stream);
#pragma warning restore SYSLIB0011
if (binaryData is JobDataMap jobDataMap)
{
// make sure we mark the map as dirty so it will be serialized as JSON next time
jobDataMap[SchedulerConstants.ForceJobDataMapDirty] = "true";
}
return binaryData;
}
}
public byte[] Serialize<T>(T obj) where T : class => jsonSerializer.Serialize(obj);
}
Customizing JSON.NET
If you need to customize JSON.NET settings, you need to inherit custom implementation and override CreateSerializerSettings.
class CustomJsonSerializer : NewtonsoftJsonObjectSerializer
{
protected override JsonSerializerSettings CreateSerializerSettings()
{
var settings = base.CreateSerializerSettings();
settings.Converters.Add(new MyCustomConverter());
return settings;
}
}
And then configure it to use
store.UseSerializer<CustomJsonSerializer>();
// or
"quartz.serializer.type" = "MyProject.CustomJsonSerializer, MyProject"
Customizing calendar serialization
If you have implemented a custom calendar, you need to implement a ICalendarSerializer for it. There's a convenience base class CalendarSerializer that you can use the get strongly-typed experience.
Custom calendar and serializer
[Serializable]
class CustomCalendar : BaseCalendar
{
public CustomCalendar()
{
}
// binary serialization support
protected CustomCalendar(SerializationInfo info, StreamingContext context) : base(info, context)
{
SomeCustomProperty = info?.GetBoolean("SomeCustomProperty") ?? true;
}
public bool SomeCustomProperty { get; set; } = true;
// binary serialization support
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
info?.AddValue("SomeCustomProperty", SomeCustomProperty);
}
}
// JSON serialization support
class CustomCalendarSerializer : CalendarSerializer<CustomCalendar>
{
protected override CustomCalendar Create(JObject source)
{
return new CustomCalendar();
}
protected override void SerializeFields(JsonWriter writer, CustomCalendar calendar)
{
writer.WritePropertyName("SomeCustomProperty");
writer.WriteValue(calendar.SomeCustomProperty);
}
protected override void DeserializeFields(CustomCalendar calendar, JObject source)
{
calendar.SomeCustomProperty = source["SomeCustomProperty"]!.Value<bool>();
}
}
Configuring custom calendar serializer
var config = SchedulerBuilder.Create();
config.UsePersistentStore(store =>
{
store.UseNewtonsoftJsonSerializer(json =>
{
json.AddCalendarSerializer<CustomCalendar>(new CustomCalendarSerializer());
});
});
Changed in 4.0
NewtonsoftJsonObjectSerializer.AddCalendarSerializer and AddTriggerSerializer were static in 3.x, so every scheduler in the process shared one set of custom serializers and registration order silently decided which one won. They have been removed. Register through the UseNewtonsoftJsonSerializer callback as above: what the callback registers belongs to that scheduler alone, so two schedulers in one container can serialize different custom types.
If you build a serializer yourself rather than through the store builder, hand it a NewtonsoftJsonSerializerRegistry. A new registry already knows every built-in trigger and calendar type, so registering a custom one adds to that set:
var registry = new NewtonsoftJsonSerializerRegistry()
.AddCalendarSerializer<CustomCalendar>(new CustomCalendarSerializer())
.AddTriggerSerializer<CustomTrigger>(new CustomTriggerSerializer());
var serializer = new NewtonsoftJsonObjectSerializer(registry);
serializer.Initialize();
