Skip to content

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);

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
}
}

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());
}
}

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")
@HandleSchedule
void weeklySync(PollData schedule) {
...
}

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=true

With 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.


  • @Periodic only applies to scheduled messages (used with @HandleSchedule)
  • The schedule automatically reschedules itself after every execution unless cancelled
  • You can:
    • Return void or null to use the same delay next time
    • Return a Duration or Instant to customize the next deadline
    • Return a new Schedule payload to completely redefine the next cycle
  • On error:
    • The default is to continue (continueOnError = true)
    • Use delayAfterError to delay retries after failure
    • Throw CancelPeriodic to stop the schedule completely
  • 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

@Periodic(delay = 60, timeUnit = TimeUnit.MINUTES, delayAfterError = 10)
@HandleSchedule
void pollExternalService(PollTask pollTask) {
try {
externalService.fetchData();
} catch (Exception e) {
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