Skip to content

Quartz.NET

Developer UtilitiesScheduler/Job ManagementC#

What it is

Quartz.NET is a full-featured job scheduler for .NET with cron triggers, persistent job stores, clustering and misfire handling.

Jobs implement IJob; triggers describe when they fire. With a persistent store, schedules survive restarts and can be coordinated across instances.

Installation

dotnet add package Quartz.Extensions.Hosting

Getting started

The smallest useful thing you can do with it, and what each part means.

Job, trigger and registration
public class ReportJob : IJob
{
    private readonly IReportService _reports;
    public ReportJob(IReportService reports) => _reports = reports;

    public async Task Execute(IJobExecutionContext context)
    {
        var date = context.MergedJobDataMap.GetString("date");
        await _reports.GenerateAsync(date, context.CancellationToken);
    }
}

builder.Services.AddQuartz(q =>
{
    var key = new JobKey("nightly-report");
    q.AddJob<ReportJob>(o => o.WithIdentity(key));
    q.AddTrigger(o => o
        .ForJob(key)
        .WithIdentity("nightly-trigger")
        .WithCronSchedule("0 0 3 * * ?", x => x.InTimeZone(TimeZoneInfo.Utc)));
});
builder.Services.AddQuartzHostedService(o => o.WaitForJobsToComplete = true);
Quartz cron has six or seven fields, not five — the leading field is seconds. Copying a Unix crontab expression directly is the most common mistake.

Advanced usage

Where the library earns its place over a simpler alternative.

Clustering and misfires
q.UsePersistentStore(s =>
{
    s.UseProperties = true;
    s.UseSqlServer(connectionString);
    s.UseClustering();   // exactly one instance runs each trigger
});

// What to do when the scheduler was down at fire time.
.WithCronSchedule("0 0 3 * * ?", x => x
    .WithMisfireHandlingInstructionDoNothing())   // skip the missed run

// Prevent overlapping executions of the same job.
[DisallowConcurrentExecution]
public class ReportJob : IJob { }
Clustering is the reason to choose Quartz over a simple timer: with a shared database, only one instance in the cluster fires each trigger, so a nightly report does not run five times.

Errors and fixes

The failures you are most likely to hit, and what actually resolves them.

CronExpression is invalid
Almost always a five-field Unix expression. Quartz needs seconds first and a ? in either day-of-month or day-of-week.
Jobs run on every instance
Clustering is not enabled, or instances share a store but not the clustering setting.

Best practices

  • Remember Quartz cron includes a seconds field; validate expressions before deploying.
  • Use UseClustering with a persistent store for multi-instance deployments.
  • Set an explicit time zone; relying on the server's local zone breaks across DST.
  • Add [DisallowConcurrentExecution] to jobs that must not overlap.

Background

Why it exists, and what it was reacting to.

A port of Java's Quartz, it is the choice when scheduling needs are genuinely complex — calendars, misfire policies and clustered execution that simpler schedulers do not address.