Skip to content

Message handling

A handler is the application code that responds to a message. Its annotation selects the kind of message; its parameter type selects the payload. A return value becomes the answer to a command or query.

Here is a query and its complete handler:

public record GetGreeting(String name) implements Request<String> {}
class GreetingHandler {
@HandleQuery
String handle(GetGreeting query) {
return "Hello, " + query.name() + "!";
}
}

Register GreetingHandler with your application, or make it a Spring @Component for discovery. Then ask the query through Fluxzero:

String greeting = Fluxzero.queryAndWait(new GetGreeting("Alice")); // "Hello, Alice!"

Request<String> declares the answer’s type. You can test this handler locally without starting a Runtime.

An event tells you something happened. A handler can start another action in response. For example, given your application’s CreateUser and SendWelcomeEmail payloads:

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

The email command has its own handler. This keeps reacting to user creation separate from sending the email. Inside handlers, static Fluxzero calls use the current application instance.

Reusable operations can keep their handler on the command/query payload itself. This is also the default for an external API interaction: the local @HandleCommand or @HandleQuery method makes the WebRequestGateway call, and other handlers dispatch its payload instead of injecting an API-service bean. No @TrackSelf or @Consumer is required merely for an external call. See Local handling and the complete outbound integration example.

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