Timeouts
Axon Framework is built with asynchronous processing at its core.
To prevent messages from being processed indefinitely, Axon provides mechanisms to set timeouts on message handler invocations and on the processing of the ProcessingContext as a whole.
Overview
Both timeouts work with a limit of the execution time (timeoutMs), and a time from which warnings will be logged (warningThreshold).
When having a long-running handler or transaction, the following will happen:
-
After the
warningThresholdhas passed, a warning will be logged. -
For each
warningIntervalthat passes after thewarningThreshold, another warning will be logged. -
When the
timeoutMshas passed, the handler or transaction will be interrupted.
Both warnings and timeouts are logged at the WARN level.
The message will contain:
-
The name of the handler with the message, or component of transaction
-
The time it has been processing so far
-
The time it has left before the timeout is reached
-
The stack trace of the handler or transaction, starting from where the timeout started
For example, a warning message could look like this:
2025-02-12T20:01:20.795Z WARN 68040 --- [playground-correlation] [ axon-janitor-0] axon-janitor : Message [io.axoniq.playground.publisher.MyContinuousEvent] for handler [io.axoniq.playground.publisher.PersistentStreamEventProcessor] is taking a long time to process. Current time: [5000ms]. Will be interrupted in [5000ms].
Stacktrace of current thread:
java.base/java.lang.Thread.sleep0(Native Method)
java.base/java.lang.Thread.sleep(Thread.java:509)
io.axoniq.playground.publisher.PersistentStreamEventProcessor.handle(PersistentStreamEventProcessor.kt:16)
java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
/** Removed some part of the stack trace for brevity **/
org.axonframework.messaging.core.timeout.TimeoutWrappedMessageHandlingMember.handle(TimeoutWrappedMessageHandlingMember.java:61)
This is beneficial for debugging and monitoring purposes, as it allows you to see which handlers are taking a long time to process. Additionally, it helps prevent a broken process from blocking processing of other messages. Examples of this are http libraries awaiting a response, that never comes during an event handler.
Defaults and disabling
Both handler timeouts and processing context timeouts are enabled by default.
Long-running handlers will start logging warnings after 10 seconds, and will be interrupted after 30 seconds. Long-running processing contexts, such as the entire duration of a command bus, query bus, or event processor invocation, will start logging warnings after 10 seconds, and will be interrupted after 60 seconds. You can always change these settings to your liking, or disable the warnings and timeouts completely as below. Note that disabling handler timeouts also disables the annotation-based timeouts.
-
Configuration API
-
Spring Boot
To disable timeouts and warnings for an application, register a HandlerTimeoutConfiguration#DISABLED and/or TimeoutUnitOfWorkFactoryConfiguration#DISABLED component with the ComponentRegistry like so:
public void configureTimeoutBehavior(MessagingConfigurer configurer) {
configurer.componentRegistry(
cr -> cr.registerComponent(
TimeoutUnitOfWorkFactoryConfiguration.class,
c -> TimeoutUnitOfWorkFactoryConfiguration.DISABLED
).registerComponent(
HandlerTimeoutConfiguration.class,
c -> HandlerTimeoutConfiguration.DISABLED
)
);
}
For Spring Boot, disabling all timeouts and warnings can be done by setting the following property in your application.properties or application.yml:
axon.timeout.enabled=false
Handler timeouts
The method you define for Axon Framework to invoke is known as a message handler.
When taking the annotation approach, these are the methods you annotate with @CommandHandler, @EventHandler, or @QueryHandler.
You can set a timeout on all message handlers, with unique configuration per message type.
-
Configuration API
-
Spring Boot
You can register a HandlerTimeoutConfigurer with the ComponentRegistry with the exact settings you would like:
public void configureTimeoutBehavior(MessagingConfigurer configurer) {
configurer.componentRegistry(cr -> cr.registerComponent(
HandlerTimeoutConfiguration.class,
c -> new HandlerTimeoutConfiguration(
HandlerTimeoutConfiguration.DEFAULT.getEvents().timeoutMs(30000),
HandlerTimeoutConfiguration.DEFAULT.getCommands(),
HandlerTimeoutConfiguration.DEFAULT.getQueries()
)
));
}
You can tweak the settings by adjusting the following properties in your application.properties or application.yml:
# For @EventHandler methods
axon.timeout.handler.events.timeout-ms=20000
axon.timeout.handler.events.warning-threshold-ms=5000
axon.timeout.handler.events.warning-interval-ms=1000
# For @CommandHandler methods
axon.timeout.handler.commands.timeout-ms=20000
axon.timeout.handler.commands.warning-threshold-ms=5000
axon.timeout.handler.commands.warning-interval-ms=1000
# For @QueryHandler methods
axon.timeout.handler.queries.timeout-ms=20000
axon.timeout.handler.queries.warning-threshold-ms=5000
axon.timeout.handler.queries.warning-interval-ms=1000
In addition, you can place a @MessageHandlerTimeout annotation on a message handler to override the default timeout.
This allows you to have a specific timeout for a message handler that you know should be faster or slower than the global configuration.
@EventHandler
@MessageHandlerTimeout(timeoutMs = 10000, warningThresholdMs = 5000, warningIntervalMs = 1000)
public void handle(Object event) throws InterruptedException {
Thread.sleep(19000);
}
Setting timeoutMs on the global configuration and the annotation to -1 will disable the timeout for that specific message handler.
Similarly, setting warningThresholdMs to -1 on both will disable the warning messages for that message handler.
Processing context timeouts
The ProcessingContext is the context in which messages are processed.
While handler timeouts only set timeouts for the invocation of handler functions, the ProcessingContext timeout sets a timeout for the entire processing of the message.
This includes loading resources (such as entities for commands), invoking the handler function, and committing the processing context.
You can customize timeouts for each component separately, such as the CommandBus, QueryBus, and EventProcessor.
|
The Timeout behavior is not yet supported for persistent streams. |
-
Configuration API
-
Spring Boot
You can register a TimeoutUnitOfWorkFactoryConfiguration component directly using the configuration API:
public void configureTimeoutBehavior(MessagingConfigurer configurer) {
configurer.componentRegistry(cr -> cr.registerComponent(
TimeoutUnitOfWorkFactoryConfiguration.class,
c -> new TimeoutUnitOfWorkFactoryConfiguration(
new TaskTimeoutSettings(30000, 25000, 1000), // command bus
new TaskTimeoutSettings(30000, 25000, 1000), // query bus
new TaskTimeoutSettings(30000, 25000, 1000), // event processors without specific settings
Map.of() // settings per named event processor
)
));
}
For more advanced scenarios, such as computing settings dynamically, you can implement a ConfigurationEnhancer to register the same configuration:
// Spring users can make a Spring bean of the ConfigurationEnhancer to auto inject it into Axon.
public class TimeoutConfigurationEnhancer implements ConfigurationEnhancer {
@Override
public void enhance(ComponentRegistry registry) {
registry.registerIfNotPresent(
TimeoutUnitOfWorkFactoryConfiguration.class,
c -> new TimeoutUnitOfWorkFactoryConfiguration(
new TaskTimeoutSettings(30000, 25000, 1000), // command bus
new TaskTimeoutSettings(30000, 25000, 1000), // query bus
new TaskTimeoutSettings(30000, 25000, 1000), // event processors without specific settings
Map.of("slow-processor", new TaskTimeoutSettings(60000, 50000, 1000))
)
);
}
}
// Somewhere in your configuration class...
public void registerTimeoutEnhancer(MessagingConfigurer configurer) {
configurer.componentRegistry(
cr -> cr.registerEnhancer(new TimeoutConfigurationEnhancer())
);
}
You can tweak the settings by adjusting the following properties in your application.properties or application.yml:
# Timeout for a specific event processor
axon.timeout.unit-of-work.event-processor.my-processor.timeout-ms=2000
axon.timeout.unit-of-work.event-processor.my-processor.warning-threshold-ms=1000
axon.timeout.unit-of-work.event-processor.my-processor.warning-interval-ms=100
# Timeout for all event processors without specific settings
axon.timeout.unit-of-work.event-processors.timeout-ms=20000
axon.timeout.unit-of-work.event-processors.warning-threshold-ms=10000
axon.timeout.unit-of-work.event-processors.warning-interval-ms=1000
# Timeout for the command bus
axon.timeout.unit-of-work.command-bus.timeout-ms=20000
axon.timeout.unit-of-work.command-bus.warning-threshold-ms=10000
axon.timeout.unit-of-work.command-bus.warning-interval-ms=1000
# Timeout for the query bus
axon.timeout.unit-of-work.query-bus.timeout-ms=20000
axon.timeout.unit-of-work.query-bus.warning-threshold-ms=10000
axon.timeout.unit-of-work.query-bus.warning-interval-ms=1000