Annotation Interface Scheduled


@Target(METHOD) @Retention(RUNTIME) public @interface Scheduled
Marks a method for automatic scheduled execution by the framework.

The annotated method must be void and take no parameters. It must be inside a @Service or other managed bean. Tasks are automatically cancelled when the owning plugin is unloaded. TaskManager walks the class hierarchy when scanning for @Scheduled methods, so an annotated method is still found on a ByteBuddy AOP proxy of the declaring bean.

Usage example:


 @Service
 public class InterestService {
     @Scheduled(period = 36000, async = true)  // Every 30 minutes, async
     public void distributeInterest() {
         // Framework calls this automatically
     }
 }
 
async = true is for literal timings only: a @Scheduled bound to a config key (below) must be sync, and async = true together with a binding refuses the module at load (#535 tracks async bindings).

Binding the timing to a config key (6.3.0)

Instead of a literal, periodKey() and delayKey() can name a @ConfigEntry path on the module's own config entity, given by config(). The value is read in seconds and multiplied by 20 for ticks, so an operator can tune the interval without a second, hand-written scheduler in the module:

 @Scheduled(config = EconomyConfig.class, periodKey = "interest.interval", delayKey = "interest.interval")
 public void distributeInterest() { ... }
 
  • The default lives only in the config field (private int interestInterval = 1800;). Leave the literal it replaces unset: period with periodKey, delay with delayKey. Setting both refuses the module at load.
  • Checked at load. The module alone is refused, naming the key and the value, when the config class is not registered exactly once for the module (a directory @ConfigEntity cannot be bound), the key matches no @ConfigEntry path, the field is not an int, long, Integer or Long, or the value is below 1 second, null or above Integer.MAX_VALUE / 20 seconds (about 3.4 years). 0 does not mean "off".
  • Declared methods only. The binding is found on the bean class's own declared methods (after unwrapping an AOP proxy), exactly like an unbound @Scheduled; a method inherited from a superclass is neither scheduled nor checked. Declare the bound method on the bean class itself (see issue #532).
  • Applied at /ul reload, keeping the task's place in its cycle: the next run is the last run plus the new period (before the first run: the arm time plus the new delay), or the next tick if that moment has already passed. A reload never runs the task early and never postpones it by restarting its clock; a task whose value did not change is not touched. An invalid value on reload is not applied -- the running value is kept and a WARNING names the key. A panel edit takes effect at the next /ul reload. The reload step runs on the main thread only.
  • Panel edits are range-checked. A panel write that sets a bound key outside this range (for example 0) is refused like a @Range violation, and nothing is written.
  • No @Range on a bound field. The binding's range above is the field's range. A module @Range on the same field would make an out-of-range reload throw from the config reload itself, which aborts the rest of that module's reload (issue #509) instead of keeping the running value.
  • Sync only. A bound method cannot be async = true; that combination is refused at load. Bind a sync task and hand the heavy work to Bukkit.getScheduler().runTaskAsynchronously(...) from its body. A sync task's runs happen on the main thread, where the reload also runs, which is what lets a reload keep the task's place in its cycle exactly. Literal (unbound) async tasks are unaffected. Issue #535 tracks allowing async bindings.
  • Modules only. A binding on an External Plugin API bean is refused.
  • Declare api-version: 630 in the module's plugin.yml. An older framework does not know these elements and silently ignores them -- the method would then run once at load instead of on the configured interval -- so the floor is what makes it refuse the module instead. 6.3.0 itself refuses a module that uses a binding while declaring a lower api-version, so the mistake shows up where it is made.
A method with no binding behaves exactly as before 6.3.0.
Since:
6.2.0
  • Optional Element Summary

    Optional Elements
    Modifier and Type
    Optional Element
    Description
    boolean
    Run on async thread instead of main server thread.
    Config entity class whose @ConfigEntry keys periodKey() and delayKey() name.
    long
    Initial delay in ticks before first execution.
    @ConfigEntry path whose value, in seconds (at least 1), is the initial delay.
    long
    Repeat interval in ticks.
    @ConfigEntry path whose value, in seconds (at least 1), is the repeat interval.
  • Element Details

    • delay

      long delay
      Initial delay in ticks before first execution. Default: 0. Leave unset when delayKey() is set.
      Returns:
      delay in ticks
      Default:
      0L
    • period

      long period
      Repeat interval in ticks. -1 = run once after delay. Default: -1. Leave unset when periodKey() is set.
      Returns:
      period in ticks
      Default:
      -1L
    • async

      boolean async
      Run on async thread instead of main server thread. Default: false
      Returns:
      true if async
      Default:
      false
    • config

      Class<? extends AbstractConfigEntity> config
      Config entity class whose @ConfigEntry keys periodKey() and delayKey() name. It must be registered exactly once for the module, and the value is read from that same instance, which /ul reload reloads in place. Default: AbstractConfigEntity itself, meaning unbound. See "Binding the timing to a config key" above.
      Returns:
      the bound config entity class
      Since:
      6.3.0
      Default:
      com.ultikits.ultitools.abstracts.AbstractConfigEntity.class
    • periodKey

      String periodKey
      @ConfigEntry path whose value, in seconds (at least 1), is the repeat interval. The path is matched as @ConfigEntry(path = ...) declares it, or the field name when the path is empty. Requires config(); excludes period(). Default: unbound.
      Returns:
      the bound period key
      Since:
      6.3.0
      Default:
      ""
    • delayKey

      String delayKey
      @ConfigEntry path whose value, in seconds (at least 1), is the initial delay. May name the same key as periodKey() -- a task that must not run at load, such as a payout, waits one full interval first. Requires config(); excludes delay(). Default: unbound.
      Returns:
      the bound delay key
      Since:
      6.3.0
      Default:
      ""