Skip to content

Message scheduling

Fluxzero allows scheduling messages for future delivery using the MessageScheduler.

Here’s an example that schedules work 30 days after an account closes and cancels it if the account reopens:

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) {
// Recheck that the account is still closed, then 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());
}
}

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

Declare @Parent on a scheduled payload to cancel pending work when its committed Model owner is deleted. See Parent-owned schedules.

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

SDK 2.x scheduling and cancellation convenience methods return without waiting for a remote acknowledgement. When you need confirmation that a schedule was stored, use the future-returning overload, for example messageScheduler.schedule(schedule, false, Guarantee.STORED), and await its future. This confirms storage, not execution of the delayed action.

See Sending messages for shared delivery settings and MessageScheduler for the exact overloads and completion contracts.


© 2026 Fluxzero