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:

  1. After the warningThreshold has passed, a warning will be logged.

  2. For each warningInterval that passes after the warningThreshold, another warning will be logged.

  3. When the timeoutMs has passed, the handler or transaction will be interrupted.

Both warnings and timeouts are logged at the WARN level. The message will contain:

  1. The name of the handler with the message, or component of transaction

  2. The time it has been processing so far

  3. The time it has left before the timeout is reached

  4. 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 EventProcessor timeout only applies to a UnitOfWork the event processor itself creates. A SubscribingEventProcessor that handles an event synchronously, as part of the ProcessingContext of the message that published it (for example, an event published directly by a command handler), does not create a UnitOfWork of its own. In that scenario, the timeout of the publishing ProcessingContext, such as the CommandBus timeout, is in charge instead.

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