agentsclimarketplace

Kora aop scheduling quartz

Skill kora-projects/kora-skills/plugins/kora-v1/skills/kora-aop-scheduling-quartz

Quartz-backed declarative scheduling in Kora via the scheduling-quartz artifact and QuartzModule. Covers @ScheduleWithCron for cron expressions, @ScheduleWithTrigger(@Tag(...)) for a custom Quartz Trigger component, @DisallowConcurrentExecution to prevent overlap, and @PersistJobDataAfterExecution for stateful jobs. Use when scheduling cron jobs, externalizing a cron via config, wiring a Quartz Trigger, or controlling concurrency and graceful shutdown of scheduled methods. For plain fixed-rate/fixed-delay timers without cron use kora-aop-scheduling-jdk instead.From its SKILL.md

Install
npx -y skills add kora-projects/kora-skills --skill kora-aop-scheduling-quartz

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

10.6 KB, ~2.6k tokens by cl100k_base, as published. Nobody here has run it

Kora Quartz Scheduling

Annotation-driven scheduling backed by the Quartz library. Annotate a method on a @Component with @ScheduleWithCron or @ScheduleWithTrigger; Kora generates the aspect at compile time and registers the job with a Quartz Scheduler.

Use Quartz when you need cron expressions or a custom Quartz Trigger. For simple fixed-rate / fixed-delay / one-shot timers, use the lighter scheduling-jdk module instead — see kora-aop-scheduling-jdk.

Class requirement: the enclosing class must be a @Component. Kora generates a separate Quartz Job wrapper at compile time, so the component class itself can stay final (Java) / non-open (Kotlin) — the canonical examples use public final class ...Scheduler. (The general "non-final/open" rule only applies to AOP aspects that wrap the method body, such as @Log or @Retry.)


Quick Start

1. Dependencies

The kora-parent BOM pins every Kora artifact — never version ru.tinkoff.kora:* deps yourself.

// build.gradle (Java)
dependencies {
    koraBom platform("ru.tinkoff.kora:kora-parent:1.2.17")
    annotationProcessor "ru.tinkoff.kora:annotation-processors"   // mandatory

    implementation "ru.tinkoff.kora:scheduling-quartz"
    implementation "ru.tinkoff.kora:config-hocon"
    implementation "ru.tinkoff.kora:logging-logback"
}
// build.gradle.kts (Kotlin)
dependencies {
    koraBom(platform("ru.tinkoff.kora:kora-parent:1.2.17"))
    ksp("ru.tinkoff.kora:symbol-processors")                      // mandatory

    implementation("ru.tinkoff.kora:scheduling-quartz")
    implementation("ru.tinkoff.kora:config-hocon")
    implementation("ru.tinkoff.kora:logging-logback")
}

2. Plug in the module

import ru.tinkoff.kora.common.KoraApp;
import ru.tinkoff.kora.config.hocon.HoconConfigModule;
import ru.tinkoff.kora.logging.logback.LogbackModule;
import ru.tinkoff.kora.scheduling.quartz.QuartzModule;

@KoraApp
public interface Application extends
    HoconConfigModule,
    LogbackModule,
    QuartzModule { }

3. A cron job

package com.example.app.jobs;

import ru.tinkoff.kora.common.Component;
import ru.tinkoff.kora.scheduling.quartz.ScheduleWithCron;

@Component
public class CronScheduler {

    @ScheduleWithCron("0 0 3 * * ?")   // daily at 03:00
    void nightlyReport() {
        // ...
    }
}

What's in this skill

FilePurpose
references/quartz-scheduling-reference.mdEvery Quartz annotation, cron grammar, config, error handling
references/scheduling-config-reference.mdFull HOCON/YAML config, telemetry, JDBC JobStore, shutdown
references/graceful-shutdown-reference.mdInterrupt handling for long-running jobs
references/jdk-scheduling-reference.mdJDK alternative (fixed-rate/delay/once) for comparison
assets/ScheduledJobs.java.templateJava jobs starter (JDK + Quartz)
assets/ScheduledJobs.kt.templateKotlin jobs starter
scripts/create-cron-job.shGenerate a cron job class + config entry
scripts/validate-cron.shSanity-check a Quartz cron expression
scripts/setup-quartz.shScaffold Quartz deps, module, config

When to use Quartz vs JDK

NeedUse
Cron expression (@ScheduleWithCron)Quartz
Custom Quartz Trigger (@ScheduleWithTrigger)Quartz
Misfire policies, calendar exclusionsQuartz
Fixed rate / fixed delay / run-onceJDK (scheduling-jdk)

Don't pull in scheduling-quartz only to run something every N seconds — @ScheduleAtFixedRate / @ScheduleWithFixedDelay from scheduling-jdk are lighter. See jdk-scheduling-reference.md.


Annotations

All Quartz annotations live in ru.tinkoff.kora.scheduling.quartz.*.

AnnotationPurpose
@ScheduleWithCronRun on a Quartz cron expression
@ScheduleWithTriggerRun on a custom org.quartz.Trigger component, referenced by @Tag
@DisallowConcurrentExecutionForbid overlapping executions of the same job
@PersistJobDataAfterExecutionRe-save org.quartz.JobDataMap after each run (use with @DisallowConcurrentExecution)

@ScheduleWithCron

@Component
public class CronScheduler {

    // Inline expression — every second (Quartz 7-field form)
    @ScheduleWithCron("* * * ? * * *")
    void everySecond() { }

    // 9 AM on weekdays
    @ScheduleWithCron("0 0 9 ? * MON-FRI")
    void morningReport() { }
}

Cron grammar and a table of common expressions are in quartz-scheduling-reference.md.

@ScheduleWithTrigger — custom Trigger

Define a Quartz Trigger as a tagged component on the @KoraApp interface, then reference it from the job method by the same @Tag. The tag is any class — the convention is to tag with the job class itself.

import org.quartz.SimpleScheduleBuilder;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import ru.tinkoff.kora.common.Tag;

@KoraApp
public interface Application extends QuartzModule {

    @Tag(TriggerScheduler.class)
    default Trigger myTrigger() {
        return TriggerBuilder.newTrigger()
            .withIdentity("myTrigger")
            .startNow()
            .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                .withIntervalInMilliseconds(50)
                .repeatForever())
            .build();
    }
}

@Component
public class TriggerScheduler {

    @ScheduleWithTrigger(@Tag(TriggerScheduler.class))
    void schedule() { }
}

@ScheduleWithTrigger takes a @Tag(...) referencing the trigger component — it does not take a string name.

@DisallowConcurrentExecution

Prevents the same job from running twice in parallel (e.g. when a run exceeds the trigger interval).

@Component
public class HourlyScheduler {

    @DisallowConcurrentExecution
    @ScheduleWithCron("0 0 * * * ?")   // top of every hour
    void hourly() { }
}

@PersistJobDataAfterExecution

Forces Quartz to re-save the org.quartz.JobDataMap after execution. Pair with @DisallowConcurrentExecution to avoid lost-update conflicts on the map.

@PersistJobDataAfterExecution
@DisallowConcurrentExecution
@ScheduleWithCron(config = "job")
void stateful() { }

Externalize cron via config

Config takes priority over the annotation value, so the schedule can be changed without recompiling. Point config at a node; Quartz reads its cron field.

@Component
public class ConfigScheduler {

    @ScheduleWithCron(config = "job")
    void schedule() { }
}
job {
  cron = "0 0 3 * * ?"   # daily at 03:00
}

You can also point config straight at a string node holding the expression (e.g. @ScheduleWithCron(config = "scheduling.jobs.quartz.cron") with scheduling.jobs.quartz.cron = "...").


Configuration essentials

Quartz native settings go under quartz as org.quartz.* properties; Kora scheduler behaviour and telemetry go under scheduling.

quartz {
  "org.quartz.threadPool.threadCount" = "10"   # default 10, RAMJobStore by default
}
scheduling {
  waitForJobComplete = true   # block graceful shutdown until current jobs finish (default false)
  telemetry {
    logging.enabled = false   # default false
    metrics.enabled = true    # default true
    tracing.enabled = true    # default true
  }
}

For JDBC JobStore persistence, clustering, telemetry tags/attributes and the full property table see scheduling-config-reference.md.


Graceful shutdown

With scheduling.waitForJobComplete = true, a graceful shutdown blocks until the running job finishes; otherwise the job thread is interrupted. Long-running jobs should check the interrupt flag and exit early:

@DisallowConcurrentExecution
@ScheduleWithCron(config = "job")
void processBatch() {
    for (var item : items) {
        if (Thread.currentThread().isInterrupted()) {
            return;   // graceful exit on shutdown
        }
        process(item);
    }
}

Full patterns (resource cleanup, partial progress, stateful jobs) are in graceful-shutdown-reference.md.


Error handling

An exception thrown from the job is logged via SLF4J. Wrap the body in try/catch when you don't want the exception to surface as a Quartz job failure:

@ScheduleWithCron("0 0 * * * ?")
void hourly() {
    try {
        doWork();
    } catch (Exception e) {
        log.error("Hourly job failed", e);
    }
}

Common pitfalls

SymptomCause / fix
Job never firesClass not a @Component, QuartzModule not added to @KoraApp, or annotation processor missing
@ScheduleWithTrigger("name") won't compileIt takes @Tag(SomeClass.class), not a string
Trigger never resolvesTag on the Trigger component and on @ScheduleWithTrigger must match exactly
Overlapping runsAdd @DisallowConcurrentExecution
Config change ignoredThe config path must match a node holding cron (or a string node)
Wrong fire timeQuartz uses the JVM default time zone; set it explicitly if needed
State lost between runsUse JDBC JobStore + @PersistJobDataAfterExecution with @DisallowConcurrentExecution

Related skills

What ships with it: 11 files

49.2 KB alongside SKILL.md, 4 of them executable

evals/

scripts/

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.