Quartz.NETQuartz.NET
Home
Features
Blog
Discussions
NuGet
GitHub
Home
Features
Blog
Discussions
NuGet
GitHub
  • Getting Started

    • Overview
    • Quartz 4 Quick Start
    • Tutorial
      • Using Quartz
      • Jobs And Triggers
      • More About Jobs & JobDetails
      • Job Data
      • More About Triggers
      • Querying Jobs and Triggers
      • Simple Triggers
      • Cron Triggers
      • RecurrenceTrigger
      • Time and TimeProvider
      • Trigger and Job Listeners
      • Scheduler Listeners
      • Job Execution Middleware
      • Job Stores
      • Configuration, Resource Usage and Building a Scheduler
      • Building a Scheduler Without a Host
      • Clustering
      • Execution Groups
      • Node Affinity (Preferred Node)
      • Testing
      • Compile-Time Checks
      • Declaring Jobs with Attributes
      • Delegate Jobs
    • Configuration Reference
    • JSON Configuration
    • Cron Expression Reference
    • Multi-Tenancy
    • Comparison
    • Frequently Asked Questions
    • Best Practices
    • Before You Go Live
    • Operating a Cluster
    • Log Events
    • Tenancy Patterns
    • Database Schema
    • Database Schema Changes
    • Migration Guide
    • Troubleshooting
    • API Documentation
  • How To's
    • One-Off Job
    • Rescheduling Jobs
    • Backfill
    • Retrying Failed Jobs
    • Pausing with a Reason
    • Job Continuations
    • Overlap Policy
    • Progress and Execution Logs
    • Job Outcomes
    • Multiple Triggers
    • Job Template
    • Running Quartz under Aspire
    • Quartz.NET with Wolverine
    • Coming from Hangfire
    • Coming from TickerQ
    • Embedding Quartz in a Library
    • Running under an External Leader Election
    • Publishing Trimmed and Native AOT
    • Extending Quartz: what is open, what is closed, and how to ask
    • A Job Store of Your Own
    • A Driver Delegate for a New Database
    • Persisting a Custom Trigger Type
    • A Lock Handler of Your Own
  • Packages

    • Quartz Core Additions

      • Jobs
      • Serialization (System.Text.Json)
      • JSON Serialization
      • Plugins
    • Integrations

      • Aspire Integration
      • ASP.NET Core Integration
      • HTTP API
      • HTTP Client
      • Dashboard
      • Hosted Services Integration
      • Microsoft DI Integration
      • Multiple Schedulers with Microsoft DI
      • Observability
      • Redis Lock Handler
      • TimeZoneConverter Integration
      • Weasel Schema Management
    • 3rd Party Plugins for Quartz
  • Quartz 3.x

    • Getting Started

      • Quartz 3 Quick Start
      • Tutorial
        • Using Quartz
        • Library Overview
        • Jobs And Triggers
        • More About Jobs
        • More About Triggers
        • Execution Groups
        • Node Affinity (Preferred Node)
        • Simple Triggers
        • Cron Triggers
        • RecurrenceTrigger
        • Trigger and Job Listeners
        • Scheduler Listeners
        • Job Stores
        • Tuning the Scheduler
        • Configuration, Resource Usage and SchedulerFactory
        • Advanced (Enterprise) Features
      • Configuration Reference
      • JSON Configuration
      • Multi-Tenancy
      • Frequently Asked Questions
      • Best Practices
      • Tenancy Patterns
      • Troubleshooting
      • API Documentation
      • Database Schema
      • Database Schema Changes
      • Migration Guide
      • Miscellaneous Features
    • How To's

      • One-Off Job
      • Multiple Triggers
      • Job Template
      • Using the CronTrigger
      • Rescheduling Jobs
    • Packages

      • Quartz Core Additions

        • Dashboard
        • Jobs
        • Serialization (System.Text.Json)
        • Serialization (Newtonsoft Json.NET)
        • Plugins
      • Integrations

        • ASP.NET Core Integration
        • Hosted Services Integration
        • Microsoft DI Integration
        • Multiple Schedulers with Microsoft DI
        • OpenTelemetry Integration
        • OpenTracing Integration
        • Redis Lock Handler
        • TimeZoneConverter Integration
      • 3rd Party Plugins for Quartz
  • Old Releases

    • Quartz 2.x
      • Quartz 2 Quick Start
      • Tutorial
        • Lesson 1: Using Quartz
        • Lesson 2: Jobs And Triggers
        • Lesson 3: More About Jobs & JobDetails
        • Lesson 4: More About Triggers
        • Lesson 5: SimpleTrigger
        • Lesson 6: CronTrigger
        • Lesson 7: TriggerListeners and JobListeners
        • Lesson 8: SchedulerListeners
        • Lesson 9: JobStores
        • Lesson 10: Configuration, Resource Usage and SchedulerFactory
        • Lesson 11: Advanced (Enterprise) Features
        • Lesson 12: Miscellaneous Features of Quartz
        • CronTrigger Tutorial
      • Configuration Reference
      • Migration Guide
      • API Documentation
    • Quartz 1.x
      • Tutorial
        • Lesson 1: Using Quartz
        • Lesson 2: Jobs And Triggers
        • Lesson 3: More About Jobs & JobDetails
        • Lesson 4: More About Triggers
        • Lesson 5: SimpleTrigger
        • Lesson 6: CronTrigger
        • Lesson 7: TriggerListeners and JobListeners
        • Lesson 8: SchedulerListeners
        • Lesson 9: JobStores
        • Lesson 10: Configuration, Resource Usage and SchedulerFactory
        • Lesson 11: Advanced (Enterprise) Features
        • Lesson 12: Miscellaneous Features of Quartz
      • API Documentation
  • License

This lesson wires up a scheduler that runs one job; the lessons that follow explain each piece of it.

Install the package

dotnet add package Quartz

That is the whole install for a hosted application. Dependency injection and the hosted service are in the core package; in 3.x they were the separate Quartz.Extensions.DependencyInjection and Quartz.Extensions.Hosting packages.

Write a job

A job is a class that implements IJob:

public sealed class HelloJob : IJob
{
    private readonly ILogger<HelloJob> logger;

    public HelloJob(ILogger<HelloJob> logger)
    {
        this.logger = logger;
    }

    public ValueTask Execute(IJobExecutionContext context, CancellationToken cancellationToken = default)
    {
        logger.LogInformation("Hello from {JobKey}", context.JobDetail.Key);
        return default;
    }
}
  • The container constructs the job for every fire, so it can take any service: a logger, a DbContext, a typed HttpClient.
  • cancellationToken is the same token as context.CancellationToken. Pass it to everything you await, so a shutdown or an Interrupt call reaches your work.

Configure the host

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.AddQuartz(q =>
{
    // run HelloJob now, and then every 40 seconds
    q.ScheduleJob<HelloJob>(trigger => trigger
        .WithIdentity("helloTrigger")
        .StartNow()
        .WithSimpleSchedule(x => x
            .WithInterval(TimeSpan.FromSeconds(40))
            .RepeatForever()));
});

builder.AddQuartzHostedService(options => options.WaitForJobsToComplete = true);

IHost host = builder.Build();

// blocks until the host is stopped, and then until the last running job completes
await host.RunAsync();
CallDoes
AddQuartzregisters the scheduler and everything it is made of
AddQuartzHostedServicestarts the scheduler with the host and shuts it down when the host stops
WaitForJobsToCompletemakes shutdown wait for running jobs instead of cancelling them

Both methods extend IHostApplicationBuilder, so they also work with WebApplication.CreateBuilder(args). When you only have the collection, use the IServiceCollection overloads (builder.Services.AddQuartz(…)).

Describing jobs and triggers

q.ScheduleJob<TJob>(…) registers one job with one trigger and takes the job's identity from the trigger's. When a job has several triggers, or is registered somewhere other than its schedule, register them separately:

builder.AddQuartz(q =>
{
    JobKey jobKey = new("reportJob");

    q.AddJob<ReportJob>(j => j
        .WithIdentity(jobKey)
        .WithDescription("nightly and on-demand sales report"));

    q.AddTrigger<ReportJob>(t => t
        .ForJob(jobKey)
        .WithIdentity("nightly")
        .WithCronSchedule("0 0 2 * * ?"));

    q.AddTrigger<ReportJob>(t => t
        .ForJob(jobKey)
        .WithIdentity("hourly-on-weekdays")
        .WithCronSchedule("0 0 9-17 ? * MON-FRI"));
});

The type argument on AddTrigger<TJob> is the job the trigger fires. It lets the trigger's data be named as properties of that job; see More About Jobs & JobDetails. Use the bare AddTrigger when the trigger only names its job by key.

"0 0 2 * * ?" is a cron expression (second, minute, hour, day-of-month, month, day-of-week): every day at 02:00. See the Cron Expression Reference. Cron is one of five schedule kinds; the others are in Lesson 2.

To also run a job each time the scheduler starts, add q.RunAtStartup(jobKey): see Multiple Triggers.

Everything registered this way is stored when the scheduler starts. With a persistent job store, registrations replace stored definitions of the same name by default, so this list describes the schedule on every start rather than seeding it once.

Scheduling at run time

Prefer the declarative registrations above for a schedule that is part of the application: the scheduler makes the store match them on every start.

For a schedule not known at startup, inject IScheduler and schedule whenever you like:

public sealed class ReportRequests
{
    private readonly IScheduler scheduler;

    public ReportRequests(IScheduler scheduler)
    {
        this.scheduler = scheduler;
    }

    public async ValueTask QueueFor(string customer, CancellationToken cancellationToken)
    {
        IJobDetail job = JobBuilder.Create<ReportJob>()
            .WithIdentity(customer, "reports")
            .UsingJobData("customer", customer)
            .Build();

        ITrigger trigger = TriggerBuilder.Create()
            .WithIdentity(customer, "reports")
            .StartAt(DateTimeOffset.UtcNow.AddMinutes(5))
            .Build();

        await scheduler.ScheduleJob(job, trigger, cancellationToken: cancellationToken);
    }
}

With several schedulers, each is registered under a name; inject one with [FromKeyedServices("reporting")] IScheduler scheduler. See Multiple schedulers.

The scheduler's lifecycle

  • Triggers do not fire until the scheduler is started. The hosted service starts it.
  • Standby() stops firing without shutting anything down; Start() resumes. Running jobs keep running.
  • Shutdown() is final. A shut-down scheduler cannot be started again; build a new one.
  • The scheduler is IAsyncDisposable. Disposing it shuts it down and releases what it owns. Under a host, the host does that.

Next: Lesson 2, a tour of jobs and triggers.

Help us by improving this page!
Last Updated: 10/5/26, 6:17 PM
Contributors: Marko Lahma, Claude Opus 5.5
Next
Jobs And Triggers