0
# Listeners and Events
1
2
Event-driven architecture for monitoring job executions, trigger firings, and scheduler lifecycle events in Quartz. The listener framework provides comprehensive hooks for observing and responding to scheduler activities.
3
4
## Capabilities
5
6
### Job Listeners
7
8
### JobListener Interface
9
10
Interface for receiving notifications about job execution lifecycle events.
11
12
```java { .api }
13
/**
14
* Interface for receiving notifications about job executions
15
*/
16
interface JobListener {
17
/**
18
* Get the unique name of this listener
19
* @return listener name for identification and management
20
*/
21
String getName();
22
23
/**
24
* Called just before a job is executed
25
* @param context the job execution context
26
*/
27
void jobToBeExecuted(JobExecutionContext context);
28
29
/**
30
* Called when job execution is vetoed by a TriggerListener
31
* @param context the job execution context
32
*/
33
void jobExecutionVetoed(JobExecutionContext context);
34
35
/**
36
* Called after a job has been executed (successfully or with exception)
37
* @param context the job execution context
38
* @param jobException the exception thrown by job (null if successful)
39
*/
40
void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException);
41
}
42
```
43
44
**Usage Examples:**
45
46
```java
47
// Basic job listener implementation
48
public class BasicJobListener implements JobListener {
49
private static final Logger logger = LoggerFactory.getLogger(BasicJobListener.class);
50
51
public String getName() {
52
return "BasicJobListener";
53
}
54
55
public void jobToBeExecuted(JobExecutionContext context) {
56
JobKey jobKey = context.getJobDetail().getKey();
57
logger.info("Job about to execute: {}", jobKey);
58
59
// Record start time in context for duration calculation
60
context.put("startTime", System.currentTimeMillis());
61
}
62
63
public void jobExecutionVetoed(JobExecutionContext context) {
64
JobKey jobKey = context.getJobDetail().getKey();
65
logger.warn("Job execution vetoed: {}", jobKey);
66
}
67
68
public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
69
JobKey jobKey = context.getJobDetail().getKey();
70
71
if (jobException == null) {
72
// Calculate execution time
73
Long startTime = (Long) context.get("startTime");
74
long duration = System.currentTimeMillis() - startTime;
75
logger.info("Job completed successfully: {} ({}ms)", jobKey, duration);
76
} else {
77
logger.error("Job failed: {} - {}", jobKey, jobException.getMessage());
78
79
// Handle specific job exception instructions
80
if (jobException.refireImmediately()) {
81
logger.info("Job will be refired immediately");
82
}
83
if (jobException.unscheduleFiringTrigger()) {
84
logger.warn("Firing trigger will be unscheduled");
85
}
86
if (jobException.unscheduleAllTriggers()) {
87
logger.error("All triggers for job will be unscheduled");
88
}
89
}
90
}
91
}
92
93
// Performance monitoring job listener
94
public class PerformanceJobListener implements JobListener {
95
private final Map<String, Long> executionTimes = new ConcurrentHashMap<>();
96
97
public String getName() {
98
return "PerformanceJobListener";
99
}
100
101
public void jobToBeExecuted(JobExecutionContext context) {
102
String fireInstanceId = context.getFireInstanceId();
103
executionTimes.put(fireInstanceId, System.currentTimeMillis());
104
}
105
106
public void jobExecutionVetoed(JobExecutionContext context) {
107
String fireInstanceId = context.getFireInstanceId();
108
executionTimes.remove(fireInstanceId);
109
}
110
111
public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
112
String fireInstanceId = context.getFireInstanceId();
113
Long startTime = executionTimes.remove(fireInstanceId);
114
115
if (startTime != null) {
116
long duration = System.currentTimeMillis() - startTime;
117
JobKey jobKey = context.getJobDetail().getKey();
118
119
// Log performance metrics
120
System.out.printf("Job %s executed in %d ms%n", jobKey, duration);
121
122
// Alert on slow jobs
123
if (duration > 30000) { // 30 seconds
124
System.err.printf("SLOW JOB ALERT: %s took %d ms%n", jobKey, duration);
125
}
126
}
127
}
128
}
129
```
130
131
### Trigger Listeners
132
133
### TriggerListener Interface
134
135
Interface for receiving notifications about trigger firing events and controlling job execution.
136
137
```java { .api }
138
/**
139
* Interface for receiving notifications about trigger firings
140
*/
141
interface TriggerListener {
142
/**
143
* Get the unique name of this listener
144
* @return listener name for identification and management
145
*/
146
String getName();
147
148
/**
149
* Called when a trigger fires (before job execution)
150
* @param trigger the trigger that fired
151
* @param context the job execution context
152
*/
153
void triggerFired(Trigger trigger, JobExecutionContext context);
154
155
/**
156
* Called to determine if job execution should be vetoed
157
* This method can prevent job execution after trigger fires
158
* @param trigger the trigger that fired
159
* @param context the job execution context
160
* @return true to veto job execution, false to allow it
161
*/
162
boolean vetoJobExecution(Trigger trigger, JobExecutionContext context);
163
164
/**
165
* Called when a trigger misfires
166
* @param trigger the trigger that misfired
167
*/
168
void triggerMisfired(Trigger trigger);
169
170
/**
171
* Called when trigger execution is complete
172
* @param trigger the trigger that completed
173
* @param context the job execution context
174
* @param triggerInstructionCode instruction for what to do next
175
*/
176
void triggerComplete(Trigger trigger, JobExecutionContext context,
177
CompletedExecutionInstruction triggerInstructionCode);
178
}
179
```
180
181
**Usage Examples:**
182
183
```java
184
// Basic trigger listener
185
public class BasicTriggerListener implements TriggerListener {
186
private static final Logger logger = LoggerFactory.getLogger(BasicTriggerListener.class);
187
188
public String getName() {
189
return "BasicTriggerListener";
190
}
191
192
public void triggerFired(Trigger trigger, JobExecutionContext context) {
193
logger.info("Trigger fired: {} for job: {}",
194
trigger.getKey(), context.getJobDetail().getKey());
195
}
196
197
public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
198
// Example: veto execution during maintenance window
199
Calendar now = Calendar.getInstance();
200
int hour = now.get(Calendar.HOUR_OF_DAY);
201
202
if (hour >= 2 && hour <= 4) { // 2 AM - 4 AM maintenance window
203
logger.warn("Vetoing job execution during maintenance window: {}",
204
context.getJobDetail().getKey());
205
return true;
206
}
207
208
return false; // Allow execution
209
}
210
211
public void triggerMisfired(Trigger trigger) {
212
logger.warn("Trigger misfired: {}", trigger.getKey());
213
}
214
215
public void triggerComplete(Trigger trigger, JobExecutionContext context,
216
CompletedExecutionInstruction triggerInstructionCode) {
217
logger.info("Trigger completed: {} with instruction: {}",
218
trigger.getKey(), triggerInstructionCode);
219
220
// Handle completion instructions
221
switch (triggerInstructionCode) {
222
case DELETE_TRIGGER:
223
logger.info("Trigger will be deleted");
224
break;
225
case SET_TRIGGER_COMPLETE:
226
logger.info("Trigger marked as complete");
227
break;
228
case RE_EXECUTE_JOB:
229
logger.info("Job will be re-executed");
230
break;
231
}
232
}
233
}
234
235
// Load balancing trigger listener
236
public class LoadBalancingTriggerListener implements TriggerListener {
237
private final AtomicInteger activeJobs = new AtomicInteger(0);
238
private final int maxConcurrentJobs;
239
240
public LoadBalancingTriggerListener(int maxConcurrentJobs) {
241
this.maxConcurrentJobs = maxConcurrentJobs;
242
}
243
244
public String getName() {
245
return "LoadBalancingTriggerListener";
246
}
247
248
public void triggerFired(Trigger trigger, JobExecutionContext context) {
249
// Track trigger firing
250
}
251
252
public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
253
int currentActive = activeJobs.get();
254
if (currentActive >= maxConcurrentJobs) {
255
System.out.printf("Vetoing job execution - too many active jobs (%d/%d)%n",
256
currentActive, maxConcurrentJobs);
257
return true;
258
}
259
260
activeJobs.incrementAndGet();
261
return false;
262
}
263
264
public void triggerMisfired(Trigger trigger) {
265
// Handle misfires
266
}
267
268
public void triggerComplete(Trigger trigger, JobExecutionContext context,
269
CompletedExecutionInstruction triggerInstructionCode) {
270
activeJobs.decrementAndGet();
271
}
272
}
273
```
274
275
### Scheduler Listeners
276
277
### SchedulerListener Interface
278
279
Interface for receiving notifications about scheduler lifecycle and management events.
280
281
```java { .api }
282
/**
283
* Interface for receiving notifications about scheduler events
284
*/
285
interface SchedulerListener {
286
/**
287
* Called when a job is scheduled
288
* @param trigger the trigger that was scheduled
289
*/
290
void jobScheduled(Trigger trigger);
291
292
/**
293
* Called when a job is unscheduled
294
* @param triggerKey the key of the unscheduled trigger
295
*/
296
void jobUnscheduled(TriggerKey triggerKey);
297
298
/**
299
* Called when a trigger is finalized (will not fire again)
300
* @param trigger the finalized trigger
301
*/
302
void triggerFinalized(Trigger trigger);
303
304
/**
305
* Called when a trigger is paused
306
* @param triggerKey the key of the paused trigger
307
*/
308
void triggerPaused(TriggerKey triggerKey);
309
310
/**
311
* Called when a group of triggers is paused
312
* @param triggerGroup the name of the paused trigger group
313
*/
314
void triggersPaused(String triggerGroup);
315
316
/**
317
* Called when a trigger is resumed
318
* @param triggerKey the key of the resumed trigger
319
*/
320
void triggerResumed(TriggerKey triggerKey);
321
322
/**
323
* Called when a group of triggers is resumed
324
* @param triggerGroup the name of the resumed trigger group
325
*/
326
void triggersResumed(String triggerGroup);
327
328
/**
329
* Called when a job is added to the scheduler
330
* @param jobDetail the added job
331
*/
332
void jobAdded(JobDetail jobDetail);
333
334
/**
335
* Called when a job is deleted from the scheduler
336
* @param jobKey the key of the deleted job
337
*/
338
void jobDeleted(JobKey jobKey);
339
340
/**
341
* Called when a job is paused
342
* @param jobKey the key of the paused job
343
*/
344
void jobPaused(JobKey jobKey);
345
346
/**
347
* Called when a group of jobs is paused
348
* @param jobGroup the name of the paused job group
349
*/
350
void jobsPaused(String jobGroup);
351
352
/**
353
* Called when a job is resumed
354
* @param jobKey the key of the resumed job
355
*/
356
void jobResumed(JobKey jobKey);
357
358
/**
359
* Called when a group of jobs is resumed
360
* @param jobGroup the name of the resumed job group
361
*/
362
void jobsResumed(String jobGroup);
363
364
/**
365
* Called when a scheduler error occurs
366
* @param msg error message
367
* @param cause the scheduler exception that occurred
368
*/
369
void schedulerError(String msg, SchedulerException cause);
370
371
/**
372
* Called when the scheduler is put in standby mode
373
*/
374
void schedulerInStandbyMode();
375
376
/**
377
* Called when the scheduler is started
378
*/
379
void schedulerStarted();
380
381
/**
382
* Called when the scheduler is starting
383
*/
384
void schedulerStarting();
385
386
/**
387
* Called when the scheduler is shutdown
388
*/
389
void schedulerShutdown();
390
391
/**
392
* Called when the scheduler is shutting down
393
*/
394
void schedulerShuttingdown();
395
396
/**
397
* Called when scheduling data is cleared
398
*/
399
void schedulingDataCleared();
400
}
401
```
402
403
**Usage Examples:**
404
405
```java
406
// Comprehensive scheduler listener
407
public class MonitoringSchedulerListener implements SchedulerListener {
408
private static final Logger logger = LoggerFactory.getLogger(MonitoringSchedulerListener.class);
409
410
// Job lifecycle events
411
public void jobScheduled(Trigger trigger) {
412
logger.info("Job scheduled: {} with trigger: {}",
413
trigger.getJobKey(), trigger.getKey());
414
}
415
416
public void jobUnscheduled(TriggerKey triggerKey) {
417
logger.info("Job unscheduled via trigger: {}", triggerKey);
418
}
419
420
public void jobAdded(JobDetail jobDetail) {
421
logger.info("Job added: {} (durable: {})",
422
jobDetail.getKey(), jobDetail.isDurable());
423
}
424
425
public void jobDeleted(JobKey jobKey) {
426
logger.info("Job deleted: {}", jobKey);
427
}
428
429
// Job state management events
430
public void jobPaused(JobKey jobKey) {
431
logger.info("Job paused: {}", jobKey);
432
}
433
434
public void jobsPaused(String jobGroup) {
435
logger.info("Jobs paused in group: {}", jobGroup);
436
}
437
438
public void jobResumed(JobKey jobKey) {
439
logger.info("Job resumed: {}", jobKey);
440
}
441
442
public void jobsResumed(String jobGroup) {
443
logger.info("Jobs resumed in group: {}", jobGroup);
444
}
445
446
// Trigger state management events
447
public void triggerPaused(TriggerKey triggerKey) {
448
logger.info("Trigger paused: {}", triggerKey);
449
}
450
451
public void triggersPaused(String triggerGroup) {
452
logger.info("Triggers paused in group: {}", triggerGroup);
453
}
454
455
public void triggerResumed(TriggerKey triggerKey) {
456
logger.info("Trigger resumed: {}", triggerKey);
457
}
458
459
public void triggersResumed(String triggerGroup) {
460
logger.info("Triggers resumed in group: {}", triggerGroup);
461
}
462
463
public void triggerFinalized(Trigger trigger) {
464
logger.info("Trigger finalized (will not fire again): {}", trigger.getKey());
465
}
466
467
// Scheduler lifecycle events
468
public void schedulerStarting() {
469
logger.info("Scheduler is starting...");
470
}
471
472
public void schedulerStarted() {
473
logger.info("Scheduler started successfully");
474
}
475
476
public void schedulerInStandbyMode() {
477
logger.info("Scheduler is in standby mode");
478
}
479
480
public void schedulerShuttingdown() {
481
logger.info("Scheduler is shutting down...");
482
}
483
484
public void schedulerShutdown() {
485
logger.info("Scheduler has been shutdown");
486
}
487
488
public void schedulingDataCleared() {
489
logger.warn("All scheduling data has been cleared");
490
}
491
492
// Error handling
493
public void schedulerError(String msg, SchedulerException cause) {
494
logger.error("Scheduler error: {} - {}", msg, cause.getMessage(), cause);
495
496
// Could implement alerting, recovery logic, etc.
497
if (cause instanceof JobPersistenceException) {
498
logger.error("Database persistence error detected");
499
}
500
}
501
}
502
503
// Statistics collecting scheduler listener
504
public class StatisticsSchedulerListener implements SchedulerListener {
505
private final AtomicInteger jobsScheduled = new AtomicInteger(0);
506
private final AtomicInteger jobsUnscheduled = new AtomicInteger(0);
507
private final AtomicInteger errors = new AtomicInteger(0);
508
private volatile boolean schedulerRunning = false;
509
510
public void jobScheduled(Trigger trigger) {
511
jobsScheduled.incrementAndGet();
512
}
513
514
public void jobUnscheduled(TriggerKey triggerKey) {
515
jobsUnscheduled.incrementAndGet();
516
}
517
518
public void schedulerStarted() {
519
schedulerRunning = true;
520
}
521
522
public void schedulerShutdown() {
523
schedulerRunning = false;
524
}
525
526
public void schedulerError(String msg, SchedulerException cause) {
527
errors.incrementAndGet();
528
}
529
530
// Getters for statistics
531
public int getJobsScheduled() { return jobsScheduled.get(); }
532
public int getJobsUnscheduled() { return jobsUnscheduled.get(); }
533
public int getErrors() { return errors.get(); }
534
public boolean isSchedulerRunning() { return schedulerRunning; }
535
536
// Other methods with empty implementations
537
public void jobAdded(JobDetail jobDetail) {}
538
public void jobDeleted(JobKey jobKey) {}
539
// ... etc
540
}
541
```
542
543
### Listener Management
544
545
### ListenerManager Interface
546
547
Interface for managing listener registration and matcher-based filtering.
548
549
```java { .api }
550
/**
551
* Interface for managing listeners with matcher-based filtering
552
*/
553
interface ListenerManager {
554
// Job listener management
555
void addJobListener(JobListener jobListener);
556
void addJobListener(JobListener jobListener, Matcher<JobKey> matcher);
557
void addJobListener(JobListener jobListener, Matcher<JobKey>... matchers);
558
void addJobListener(JobListener jobListener, List<Matcher<JobKey>> matchers);
559
boolean addJobListenerMatcher(String listenerName, Matcher<JobKey> matcher);
560
boolean removeJobListenerMatcher(String listenerName, Matcher<JobKey> matcher);
561
Set<Matcher<JobKey>> getJobListenerMatchers(String listenerName);
562
boolean setJobListenerMatchers(String listenerName, List<Matcher<JobKey>> matchers);
563
boolean removeJobListener(String name);
564
List<JobListener> getJobListeners();
565
JobListener getJobListener(String name);
566
567
// Trigger listener management
568
void addTriggerListener(TriggerListener triggerListener);
569
void addTriggerListener(TriggerListener triggerListener, Matcher<TriggerKey> matcher);
570
void addTriggerListener(TriggerListener triggerListener, Matcher<TriggerKey>... matchers);
571
void addTriggerListener(TriggerListener triggerListener, List<Matcher<TriggerKey>> matchers);
572
boolean addTriggerListenerMatcher(String listenerName, Matcher<TriggerKey> matcher);
573
boolean removeTriggerListenerMatcher(String listenerName, Matcher<TriggerKey> matcher);
574
Set<Matcher<TriggerKey>> getTriggerListenerMatchers(String listenerName);
575
boolean setTriggerListenerMatchers(String listenerName, List<Matcher<TriggerKey>> matchers);
576
boolean removeTriggerListener(String name);
577
List<TriggerListener> getTriggerListeners();
578
TriggerListener getTriggerListener(String name);
579
580
// Scheduler listener management
581
void addSchedulerListener(SchedulerListener schedulerListener);
582
boolean removeSchedulerListener(SchedulerListener schedulerListener);
583
List<SchedulerListener> getSchedulerListeners();
584
}
585
```
586
587
**Usage Examples:**
588
589
```java
590
// Register listeners with the scheduler
591
Scheduler scheduler = new StdSchedulerFactory().getScheduler();
592
ListenerManager listenerManager = scheduler.getListenerManager();
593
594
// Add job listeners
595
JobListener performanceListener = new PerformanceJobListener();
596
JobListener loggingListener = new BasicJobListener();
597
598
// Global job listener (listens to all jobs)
599
listenerManager.addJobListener(loggingListener);
600
601
// Job listener with specific matcher (only "report" group jobs)
602
listenerManager.addJobListener(performanceListener,
603
GroupMatcher.jobGroupEquals("reports"));
604
605
// Job listener with multiple matchers
606
listenerManager.addJobListener(performanceListener,
607
GroupMatcher.jobGroupEquals("reports"),
608
GroupMatcher.jobGroupEquals("analytics"));
609
610
// Add trigger listeners
611
TriggerListener loadBalancer = new LoadBalancingTriggerListener(5);
612
TriggerListener basicTriggerListener = new BasicTriggerListener();
613
614
// Global trigger listener
615
listenerManager.addTriggerListener(basicTriggerListener);
616
617
// Trigger listener for specific group
618
listenerManager.addTriggerListener(loadBalancer,
619
GroupMatcher.triggerGroupEquals("batch"));
620
621
// Add scheduler listeners
622
SchedulerListener monitoringListener = new MonitoringSchedulerListener();
623
SchedulerListener statsListener = new StatisticsSchedulerListener();
624
625
listenerManager.addSchedulerListener(monitoringListener);
626
listenerManager.addSchedulerListener(statsListener);
627
628
// Managing listener matchers
629
String listenerName = performanceListener.getName();
630
631
// Add additional matcher to existing listener
632
listenerManager.addJobListenerMatcher(listenerName,
633
KeyMatcher.keyEquals(jobKey("importantJob", "critical")));
634
635
// Replace all matchers for a listener
636
List<Matcher<JobKey>> newMatchers = Arrays.asList(
637
GroupMatcher.jobGroupEquals("highPriority"),
638
GroupMatcher.jobGroupContains("urgent")
639
);
640
listenerManager.setJobListenerMatchers(listenerName, newMatchers);
641
642
// Remove specific matcher
643
listenerManager.removeJobListenerMatcher(listenerName,
644
GroupMatcher.jobGroupEquals("reports"));
645
646
// Remove listeners
647
listenerManager.removeJobListener(performanceListener.getName());
648
listenerManager.removeTriggerListener(loadBalancer.getName());
649
listenerManager.removeSchedulerListener(statsListener);
650
```
651
652
### Matcher Classes
653
654
Utility classes for matching jobs and triggers in listener registration.
655
656
```java { .api }
657
/**
658
* Matchers for filtering which jobs/triggers listeners should monitor
659
*/
660
class GroupMatcher<T extends Key<T>> {
661
static <T extends Key<T>> GroupMatcher<T> groupEquals(String group);
662
static <T extends Key<T>> GroupMatcher<T> groupStartsWith(String prefix);
663
static <T extends Key<T>> GroupMatcher<T> groupEndsWith(String suffix);
664
static <T extends Key<T>> GroupMatcher<T> groupContains(String substring);
665
static <T extends Key<T>> GroupMatcher<T> anyGroup();
666
}
667
668
class KeyMatcher<T extends Key<T>> {
669
static <T extends Key<T>> KeyMatcher<T> keyEquals(T key);
670
}
671
672
class NameMatcher<T extends Key<T>> {
673
static <T extends Key<T>> NameMatcher<T> nameEquals(String name);
674
static <T extends Key<T>> NameMatcher<T> nameStartsWith(String prefix);
675
static <T extends Key<T>> NameMatcher<T> nameEndsWith(String suffix);
676
static <T extends Key<T>> NameMatcher<T> nameContains(String substring);
677
}
678
679
class AndMatcher<T extends Key<T>> {
680
static <T extends Key<T>> AndMatcher<T> and(Matcher<T>... matchers);
681
}
682
683
class OrMatcher<T extends Key<T>> {
684
static <T extends Key<T>> OrMatcher<T> or(Matcher<T>... matchers);
685
}
686
687
class NotMatcher<T extends Key<T>> {
688
static <T extends Key<T>> NotMatcher<T> not(Matcher<T> matcher);
689
}
690
```
691
692
**Usage Examples:**
693
694
```java
695
// Various matcher patterns
696
import static org.quartz.impl.matchers.GroupMatcher.*;
697
import static org.quartz.impl.matchers.KeyMatcher.*;
698
import static org.quartz.impl.matchers.NameMatcher.*;
699
700
// Group-based matching
701
listenerManager.addJobListener(listener, jobGroupEquals("reports"));
702
listenerManager.addJobListener(listener, jobGroupStartsWith("batch"));
703
listenerManager.addJobListener(listener, jobGroupContains("urgent"));
704
705
// Name-based matching
706
listenerManager.addJobListener(listener, jobNameEquals("importantJob"));
707
listenerManager.addJobListener(listener, jobNameEndsWith("Report"));
708
709
// Key-based matching
710
listenerManager.addJobListener(listener,
711
keyEquals(jobKey("specificJob", "specificGroup")));
712
713
// Complex matching with boolean operators
714
listenerManager.addJobListener(listener,
715
and(jobGroupEquals("reports"), jobNameEndsWith("Daily")));
716
717
listenerManager.addJobListener(listener,
718
or(jobGroupEquals("urgent"), jobGroupEquals("critical")));
719
720
listenerManager.addJobListener(listener,
721
not(jobGroupEquals("lowPriority")));
722
723
// Match everything
724
listenerManager.addJobListener(listener, anyJobGroup());
725
listenerManager.addTriggerListener(triggerListener, anyTriggerGroup());
726
```