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
    • Configuration Reference
    • JSON Configuration
    • Cron Expression Reference
    • Multi-Tenancy
    • 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
    • Retrying Failed Jobs
    • Multiple Triggers
    • Job Template
    • Running Quartz under Aspire
    • Quartz.NET with Wolverine
    • 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
    • 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

Scheduler Listeners

SchedulerListeners are much like ITriggerListeners and IJobListeners, except they receive notification of events within the scheduler itself - not necessarily events related to a specific trigger or job.

Scheduler-related events include: the addition of a job/trigger, the removal of a job/trigger, a serious error within the scheduler, notification of the scheduler being shutdown, and others.

Caution

Make sure your scheduler listeners never throw an exception (use a try-catch) and that they can handle internal problems. Quartz can get in unpredictable state when it is unable to determine whether required logic in listener was completed successfully when listener notification failed.

The ISchedulerListener Interface

public interface ISchedulerListener
{
    string Name => GetType().Name;

    ValueTask JobScheduled(IScheduler scheduler, ITrigger trigger, CancellationToken cancellationToken = default);

    ValueTask JobUnscheduled(IScheduler scheduler, TriggerKey triggerKey, CancellationToken cancellationToken = default);

    ValueTask TriggerFinalized(IScheduler scheduler, ITrigger trigger, CancellationToken cancellationToken = default);

    ValueTask TriggersPaused(IScheduler scheduler, string? triggerGroup, CancellationToken cancellationToken = default);

    ValueTask TriggersResumed(IScheduler scheduler, string? triggerGroup, CancellationToken cancellationToken = default);

    ValueTask JobsPaused(IScheduler scheduler, string? jobGroup, CancellationToken cancellationToken = default);

    ValueTask JobsResumed(IScheduler scheduler, string? jobGroup, CancellationToken cancellationToken = default);

    ValueTask SchedulerError(IScheduler scheduler, SchedulerErrorContext errorContext, CancellationToken cancellationToken = default);

    ValueTask SchedulerShutdown(IScheduler scheduler, CancellationToken cancellationToken = default);

    // ...and the rest; every member has a do-nothing default implementation, so implement only what you care about
}

A null group in JobsPaused, JobsResumed, TriggersPaused or TriggersResumed means every group.

Every callback names its scheduler

A listener reaches the scheduler it serves through its execution context, or as its first argument when there is no execution. Nothing here runs inside a firing, so every member takes the scheduler — which is what lets one listener instance serve several schedulers in one host and still say which of them it is hearing from:

public sealed class AuditSchedulerListener : ISchedulerListener
{
    public ValueTask TriggerPaused(IScheduler scheduler, TriggerKey triggerKey, CancellationToken cancellationToken = default)
    {
        logger.LogInformation("{SchedulerName} paused {TriggerKey}", scheduler.SchedulerName, triggerKey);
        return default;
    }
}

It is the scheduler itself rather than its name, so a listener that wants to act on what it heard can: pause the trigger, read Status, ask for the job. SchedulerName and SchedulerInstanceId are on it when identity is all you need.

Reporting an error

SchedulerError is raised when something goes seriously wrong — a job that could not be built, a job store that keeps failing, a job that threw. It receives a SchedulerErrorContext, which says what went wrong and, where the scheduler knew it, what it went wrong for:

public sealed record SchedulerErrorContext
{
    public required string Message { get; init; }
    public required SchedulerException Exception { get; init; }
    public TriggerKey? TriggerKey { get; init; }
    public JobKey? JobKey { get; init; }
    public string? FireInstanceId { get; init; }
}

The three keys are null when there is nothing to name — a scan that never reached a trigger, a store retrying a connection. Every failure inside a firing fills in all three:

public ValueTask SchedulerError(IScheduler scheduler, SchedulerErrorContext errorContext, CancellationToken cancellationToken = default)
{
    if (errorContext.TriggerKey is { } triggerKey)
    {
        return scheduler.PauseTrigger(triggerKey, cancellationToken);
    }

    logger.LogError(errorContext.Exception, "{Message}", errorContext.Message);
    return default;
}

SchedulerListeners are registered with the scheduler's ListenerManager. SchedulerListeners can be virtually any object that implements the ISchedulerListener interface.

A scheduler listener is identified by its Name, which defaults to the type's name. Registering a second listener under a name that is already taken replaces the first, so override Name if you register several instances of one type with the same scheduler.

Adding a SchedulerListener:

scheduler.ListenerManager.AddSchedulerListener(mySchedListener);

Removing a SchedulerListener:

scheduler.ListenerManager.RemoveSchedulerListener(mySchedListener.Name);

A listener that belongs to the application, rather than to a moment in its run, is better registered where the scheduler is configured — it is then constructed from the container, and it is in place before the scheduler starts, so it hears the starting and started notifications too:

builder.AddQuartz(q =>
{
    q.AddSchedulerListener<AuditSchedulerListener>();
});

There are overloads for an instance you built yourself and for a factory over the service provider, matching the ones for job and trigger listeners.

Help us by improving this page!
Last Updated: 9/9/26, 7:08 PM
Contributors: Marko Lahma, Claude Fable 5.1
Prev
Trigger and Job Listeners
Next
Job Execution Middleware