Local handling
Fluxzero supports both tracked and local message handling. Local handlers process messages in the publishing thread, without persisting them or waiting for a tracker by default. Calls still go through the Fluxzero dispatch and handler APIs; local handling does not mean calling the handler method directly.
Use a local handler when the calling application can perform the operation itself. For example, a greeting query needs no stored delivery:
record GetGreeting(String name) implements Request<String> {}
@LocalHandlerclass GreetingHandler { @HandleQuery String handle(GetGreeting query) { return "Hello, " + query.name() + "!"; }}data class GetGreeting(val name: String) : Request<String>
@LocalHandlerclass GreetingHandler { @HandleQuery fun handle(query: GetGreeting) = "Hello, ${query.name}!"}Register the handler normally, then call Fluxzero.queryAndWait(...). @LocalHandler can also be placed on a
method or package. Add logMetrics = true if you need metrics for these local calls; they are off by default.
Require local handling
Section titled “Require local handling”Use @LocalOnly sparingly on a payload or package when external publication would cross a security boundary. Fluxzero
then invokes local handlers only, suppresses logMessage, returns a failed future for an unhandled request and quietly
completes an unhandled non-request. Parent packages include their children; @LocalOnly(false) restores normal
fallback for a more specific package or payload type.
Self-handling messages
Section titled “Self-handling messages”For a reusable operation, put the handler on its payload. The same greeting then needs only one type, with no
registration or @LocalHandler annotation:
public record GetGreeting(String name) implements Request<String> { @HandleQuery String handle() { return "Hello, " + name + "!"; }}data class GetGreeting(val name: String) : Request<String> { @HandleQuery fun handle() = "Hello, $name!"}Call it through Fluxzero so validation, interceptors and message context still apply. Local self-handling is also the default pattern for reusable external API operations; see Sending web requests.
Persisting a self-handling request
Section titled “Persisting a self-handling request”By default, such handlers are local in both synchronous and asynchronous fixtures. Use @TrackSelf only when the
command/query itself needs persisted delivery through the Runtime, its own consumer position or independent
replay/retry. Calling an external API, waiting for its response or wanting HTTP audit logs does not require it.
@Consumer configures that tracked handler when a custom configuration is needed:
@TrackSelf@Consumer(name = "user-management")public record GetGreeting(String name) implements Request<String> {
@HandleQuery String handle() { return "Hello, " + name + "!"; }}@TrackSelf@Consumer(name = "user-management")data class GetGreeting(val name: String) : Request<String> {
@HandleQuery fun handle(): String { return "Hello, $name!" }}When component-scanned (e.g., via Spring), @TrackSelf classes will be automatically discovered and registered. This works even if the annotation is placed on an interface rather than the concrete class—allowing for reusable handler patterns.
Use a shared @TrackSelf interface when several payload types should have the same tracked behavior.
Register the tracked payloads with the application that owns them; ordinary self-handling payloads remain local.
Local-only Model commands and tests
Section titled “Local-only Model commands and tests”@LocalOnly prevents external fallback; it does not register or promote a handler. A command with only Model
@Apply methods therefore still needs a real local handler. For an intentional process-local execution boundary,
a @HandleCommand method on the payload can call Fluxzero.assertAndApply(this) (Java or Kotlin). This retains
assertions and atomic Model commits. Ordinary Model commands should keep their automatic handling without a
pass-through handler. The Models’ persistence and event publication remain separate policies.
Synchronous TestFixture tracking simulation does not satisfy a missing local handler. Both fixture modes reject
unhandled local-only requests immediately, without waiting for external consumers. Commands actually handled
locally, explicitly registered local handlers, and ordinary tracked messages retain their existing behavior.
A restricted dispatch stays restricted when an interceptor replaces its payload with an unmarked type.
© 2026 Fluxzero