Message scheduling
Fluxzero allows scheduling messages for future delivery using the MessageScheduler.
Use ScheduleId when different schedule categories can share the same domain ID. Fluxzero uses its stable type:id
representation for scheduling, lookup and cancellation.
ScheduleId expiryId = ScheduleId.of("account-expiry", userId);Fluxzero.schedule(new TerminateAccount(userId), expiryId, Duration.ofDays(30));Fluxzero.cancelSchedule(expiryId);val expiryId = ScheduleId.of("account-expiry", userId)Fluxzero.schedule(TerminateAccount(userId), expiryId, Duration.ofDays(30))Fluxzero.cancelSchedule(expiryId)Here’s an example that schedules a termination event 30 days after an account is closed:
class UserLifecycleHandler { @HandleEvent void handle(AccountClosed event) { Fluxzero.schedule( new TerminateAccount(event.getUserId()), "AccountClosed-" + event.getUserId(), Duration.ofDays(30) ); }
@HandleEvent void handle(AccountReopened event) { Fluxzero.cancelSchedule("AccountClosed-" + event.getUserId()); }
@HandleSchedule void handle(TerminateAccount schedule) { // Perform termination }}class UserLifecycleHandler {
@HandleEvent fun handle(event: AccountClosed) { Fluxzero.schedule( TerminateAccount(event.userId), "AccountClosed-${event.userId}", Duration.ofDays(30) ) }
@HandleEvent fun handle(event: AccountReopened) { Fluxzero.cancelSchedule("AccountClosed-${event.userId}") }
@HandleSchedule fun handle(schedule: TerminateAccount) { // Perform termination }}Scheduling commands
Section titled “Scheduling commands”You can also schedule commands directly using scheduleCommand(...).
class UserLifecycleHandler { @HandleEvent void handle(AccountClosed event) { Fluxzero.scheduleCommand( new TerminateAccount(event.getUserId()), "AccountClosed-" + event.getUserId(), Duration.ofDays(30)); }
@HandleEvent void handle(AccountReopened event) { Fluxzero.cancelSchedule("AccountClosed-" + event.getUserId()); }}class UserLifecycleHandler {
@HandleEvent fun handle(event: AccountClosed) { Fluxzero.scheduleCommand( TerminateAccount(event.userId), "AccountClosed-${event.userId}", Duration.ofDays(30) ) }
@HandleEvent fun handle(event: AccountReopened) { Fluxzero.cancelSchedule("AccountClosed-${event.userId}") }}Periodic scheduling
Section titled “Periodic scheduling”Fluxzero supports recurring message schedules via the @Periodic annotation. This makes it easy to run background tasks on a fixed interval or cron schedule.
You can apply @Periodic to a schedule payload or a @HandleSchedule method.
@Periodic(delay = 5, timeUnit = TimeUnit.MINUTES)public record RefreshData(String index) {}@Periodic(cron = "0 0 * * MON", timeZone = "Europe/Amsterdam")@HandleSchedulevoid weeklySync(PollData schedule) { ...}@Periodic(delay = 5, timeUnit = TimeUnit.MINUTES)data class RefreshData(val index: String)@Periodic(cron = "0 0 * * MON", timeZone = "Europe/Amsterdam")@HandleSchedulefun weeklySync(schedule: PollData) { ...}Initial autostart timing
Section titled “Initial autostart timing”initialDelay defaults to -1, which means no explicit initial delay was configured. Compatibility defaults treat that
implicit value as 0, so an auto-started periodic schedule starts immediately. New defaults can be selected with:
fluxzero.defaults.version=2026.05.21# equivalent explicit setting:fluxzero.scheduling.periodic.useDefaultInitialDelay=trueWith this behavior, fixed-delay schedules first run after delay, and cron schedules first run at the next cron match.
For example, @Periodic(delay = 60_000) first runs after 60 seconds, while
@Periodic(cron = "*/5 * * * *") first runs at the next five-minute boundary. Set initialDelay = 0 when the first
run should be immediate.
Behavior and advanced options
Section titled “Behavior and advanced options”@Periodiconly applies to scheduled messages (used with@HandleSchedule)- The schedule automatically reschedules itself after every execution unless cancelled
- You can:
- Return
voidornullto use the same delay next time - Return a
DurationorInstantto customize the next deadline - Return a new
Schedulepayload to completely redefine the next cycle
- Return
- On error:
- The default is to continue (
continueOnError = true) - Use
delayAfterErrorto delay retries after failure - Throw
CancelPeriodicto stop the schedule completely
- The default is to continue (
- Use
@Periodic(autoStart = false)to prevent the schedule from activating on startup - The schedule ID defaults to the payload class name but can be customized using
scheduleId
Example: polling with error fallback
Section titled “Example: polling with error fallback”@Periodic(delay = 60, timeUnit = TimeUnit.MINUTES, delayAfterError = 10)@HandleSchedulevoid pollExternalService(PollTask pollTask) { try { externalService.fetchData(); } catch (Exception e) { log.warn("Polling failed, will retry in 10 minutes", e); throw e; }}@Periodic(delay = 60, timeUnit = TimeUnit.MINUTES, delayAfterError = 10)@HandleSchedulefun pollExternalService(task: PollTask) { try { externalService.fetchData() } catch (e: Exception) { log.warn("Polling failed, will retry in 10 minutes", e) throw e }}In this example:
- The task runs every hour
- If it fails, it retries after 10 minutes
- If it succeeds, it returns to the hourly schedule
© 2026 Fluxzero