Skip to content

Message handling

Fluxzero is centered around sending and receiving messages — such as commands, events, queries, and web requests. These messages can originate from your own application or any other client connected to the same Fluxzero Runtime.

Handlers are simply methods annotated with @HandleCommand, @HandleEvent, @HandleQuery, etc. Here’s a basic example of an event handler that dispatches a command to send a welcome email when a user is created:

class UserEventHandler {
@HandleEvent
void handle(CreateUser event) {
Fluxzero.sendCommand(new SendWelcomeEmail(event.getUserProfile()));
}
}

This handler uses the static sendCommand method on Fluxzero, which works because the client is automatically injected into the thread-local context before message handling begins. This eliminates the need to inject Fluxzero into every handler.


To receive that command, define a corresponding command handler:

class EmailCommandHandler {
@HandleCommand
void handle(SendWelcomeEmail command) {
// send welcome email to user
}
}

Handlers can return a result (e.g., from queries or commands). The result is automatically published as a Result message and sent back to the originating client:

class UserQueryHandler {
@HandleQuery
UserProfile handle(GetUserProfile query) {
// return the user profile
return new UserProfile(...);
}
}

To perform a query and wait synchronously for its result:

class PasswordEventHandler {
@HandleEvent
void handle(ResetPassword event) {
UserProfile user = Fluxzero.queryAndWait(new GetUserProfile(event.getUserId()));
// perform reset using user
}
}

Handler methods may also return a CompletableFuture<T> instead of a direct value. Fluxzero will publish the result when the future completes:

class AsyncUserQueryHandler {
@HandleQuery
CompletableFuture<UserProfile> handle(GetUserProfile query) {
return userService.fetchAsync(query.getUserId());
}
}

Fluxzero resolves which handler(s) should run based on message type and specificity.

If multiple methods in the same class match a message, only the most specific one is invoked.

class SpecificityExample {
@HandleEvent
void handle(Object event) { /* generic fallback */ }
@HandleEvent
void handle(CreateUser event) { /* specific handler */ }
}

When different classes handle the same payload, each eligible handler runs independently.

class BusinessHandler {
@HandleEvent
void handle(CreateUser event) { /* business logic */ }
}
class LoggingHandler {
@HandleEvent
void log(Object event) { /* audit/metrics */ }
}

For commands, queries, and web requests, a single non-passive handler should produce the response. Additional passive handlers can observe for metrics/auditing.

class UserHandler {
@HandleQuery
UserAccount handle(GetUser query) {
return userRepository.find(query.getUserId());
}
}
class QueryMetricsHandler {
@HandleQuery(passive = true)
void record(Object query) {
metrics.increment("queries." + query.getClass().getSimpleName());
}
}

Fluxzero allows fine-grained control over handler method parameters using the ParameterResolver interface. This lets you inject any value into annotated handler methods — beyond just the payload or Message metadata.

When a message is dispatched to a handler (e.g. via @HandleEvent, @HandleCommand, etc.), the framework inspects the method’s parameters and resolves each one using the configured ParameterResolvers.

By default, Fluxzero supports injection of the following:

  • The message payload (automatically matched by parameter type)
  • The full Message, Schedule, or WebRequest
  • The raw DeserializingMessage (for low-level access)
  • The message Metadata
  • The currently authenticated User (if available)
  • The associated Entity wrapper or the entity value itself
  • The triggering message (annotated with @Trigger)
  • Any Spring bean (when Spring integration is enabled)

Other contextual values like message ID or timestamp can be obtained from the Message:

@HandleEvent
void handle(CreateUser event, Message message) {
log.info("User created at {}", message.getTimestamp());
}

You can create a custom resolver to inject arbitrary values, such as headers, timestamps, or contextual objects:

public class TimestampParameterResolver implements ParameterResolver<DeserializingMessage> {
@Override
public Function<DeserializingMessage, Object> resolve(Parameter parameter, Annotation methodAnnotation) {
if (parameter.getType().equals(Instant.class)) {
return DeserializingMessage::getTimestamp;
}
return null;
}
}

Then register it via your builder: Assuming you are configuring a FluxzeroBuilder builder:

builder
.addParameterResolver(new TimestampParameterResolver())
.build();

And use it in your handler:

@HandleCommand
void handle(CreateOrder command, Instant timestamp) {
log.info("Command received at {}", timestamp);
}
  • Injecting request-specific context (e.g. tracing info)
  • Supporting custom annotations (e.g. @FromHeader)
  • Enabling access to correlated data (e.g. parent entity)
  • Binding to environment or system values

Custom parameter injection is a powerful tool for modular, contextual logic. It works seamlessly with all handler annotations (@HandleEvent, @HandleCommand, @HandleQuery, @HandleError, etc.) and helps avoid boilerplate argument passing.

A HandlerInterceptor lets you wrap or intercept the execution of handler methods. This gives you a central place to add cross‑cutting behavior such as:

  • Authorization & access control — block or allow messages depending on the current user
  • Auditing & logging — capture details about incoming messages, handler calls, or results
  • Validation — enforce extra business rules before or after handler execution
  • Blocking — stop the handler from being invoked entirely
  • Result transformation — adjust or enrich results before they are published
  • Thread context propagation — set up thread‑local state (e.g. correlation IDs, security principals)
public class AuthorizationInterceptor implements HandlerInterceptor {
@Override
public Function<DeserializingMessage, Object> interceptHandling(
Function<DeserializingMessage, Object> next, HandlerInvoker invoker) {
return message -> {
if (!isAuthorized(message)) {
throw new UnauthorizedException();
}
return next.apply(message);
};
}
}

You can register handler interceptors either per class (via @Consumer annotation) or globally across the application. See Configuring Fluxzero for details.

The diagram below shows how Fluxzero selects and invokes handlers for an incoming message:

graph TD
    MSG[Incoming message] --> MATCH[Match handlers by signature & parameters]
    MATCH --> FOUND_MATCH{Handler found?}
    FOUND_MATCH -->|Yes| INTERCEPT[Apply handler interceptors]
    INTERCEPT --> PRE_BLOCK{Handling blocked?}
    PRE_BLOCK -->|No| INVOKE[Provide method parameters]
    INVOKE --> DESERIALIZE{Payload already deserialized?}
    DESERIALIZE -->|No| UPCAST[Upcast payload if needed]
    UPCAST --> MAP[Map to payload object]
    MAP --> HANDLED[Invoke handler method]
    DESERIALIZE -->|Yes| HANDLED
  • Handlers are selected based on method signatures and parameter types.
  • Interceptors can wrap the call or block invocation altogether.
  • On invocation, the message payload is deserialized if needed, which may include upcasting followed by mapping to the target object type.

© 2026 Fluxzero